diff options
author | daniel oeh <daniel.oeh@gmail.com> | 2014-10-09 21:29:30 +0200 |
---|---|---|
committer | daniel oeh <daniel.oeh@gmail.com> | 2014-10-09 21:29:30 +0200 |
commit | baa7d5f11283cb7668d45b561af5d38f0ccb9632 (patch) | |
tree | 21cb976e8f2a948dae44bc014fb2c6ed62f81157 /src/de/danoeh/antennapod | |
parent | a8bf235017d5896c0691ad056727dafc72c63596 (diff) | |
parent | 4d622cb27ab54dc081d81285128b9c70f8dd37ac (diff) | |
download | AntennaPod-baa7d5f11283cb7668d45b561af5d38f0ccb9632.zip |
Merge branch 'develop'0.9.9.4
Diffstat (limited to 'src/de/danoeh/antennapod')
33 files changed, 691 insertions, 482 deletions
diff --git a/src/de/danoeh/antennapod/AppConfig.java b/src/de/danoeh/antennapod/AppConfig.java index 7a75e3a18..24f13d4a3 100644 --- a/src/de/danoeh/antennapod/AppConfig.java +++ b/src/de/danoeh/antennapod/AppConfig.java @@ -2,6 +2,6 @@ package de.danoeh.antennapod; public final class AppConfig { /** Should be used when setting User-Agent header for HTTP-requests. */ - public final static String USER_AGENT = "AntennaPod/0.9.9.3"; + public final static String USER_AGENT = "AntennaPod/0.9.9.4"; } diff --git a/src/de/danoeh/antennapod/activity/AudioplayerActivity.java b/src/de/danoeh/antennapod/activity/AudioplayerActivity.java index 50f5d8f2e..18d27ddda 100644 --- a/src/de/danoeh/antennapod/activity/AudioplayerActivity.java +++ b/src/de/danoeh/antennapod/activity/AudioplayerActivity.java @@ -343,7 +343,6 @@ public class AudioplayerActivity extends MediaplayerActivity implements ItemDesc } else { ft.add(R.id.contentView, currentlyShownFragment); } - ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN); ft.disallowAddToBackStack(); ft.commit(); updateNavButtonDrawable(); @@ -383,6 +382,7 @@ public class AudioplayerActivity extends MediaplayerActivity implements ItemDesc public void run() { PicassoProvider.getMediaMetadataPicassoInstance(AudioplayerActivity.this) .load(media.getImageUri()) + .fit() .into(butNavLeft); } }); @@ -399,6 +399,7 @@ public class AudioplayerActivity extends MediaplayerActivity implements ItemDesc public void run() { PicassoProvider.getMediaMetadataPicassoInstance(AudioplayerActivity.this) .load(media.getImageUri()) + .fit() .into(butNavLeft); } diff --git a/src/de/danoeh/antennapod/activity/DefaultOnlineFeedViewActivity.java b/src/de/danoeh/antennapod/activity/DefaultOnlineFeedViewActivity.java index 86b278bf0..e8bc75293 100644 --- a/src/de/danoeh/antennapod/activity/DefaultOnlineFeedViewActivity.java +++ b/src/de/danoeh/antennapod/activity/DefaultOnlineFeedViewActivity.java @@ -122,11 +122,10 @@ public class DefaultOnlineFeedViewActivity extends OnlineFeedViewActivity { subscribeButton = (Button) header.findViewById(R.id.butSubscribe); - if (feed.getImage() != null) { - int imageSize = (int) getResources().getDimension(R.dimen.thumbnail_length); + if (feed.getImage() != null && StringUtils.isNoneBlank(feed.getImage().getDownload_url())) { PicassoProvider.getDefaultPicassoInstance(this) .load(feed.getImage().getDownload_url()) - .resize(imageSize, imageSize) + .fit() .into(cover); } diff --git a/src/de/danoeh/antennapod/activity/FeedInfoActivity.java b/src/de/danoeh/antennapod/activity/FeedInfoActivity.java index b46bc7546..5cf187eb6 100644 --- a/src/de/danoeh/antennapod/activity/FeedInfoActivity.java +++ b/src/de/danoeh/antennapod/activity/FeedInfoActivity.java @@ -80,6 +80,7 @@ public class FeedInfoActivity extends ActionBarActivity { public void run() { PicassoProvider.getDefaultPicassoInstance(FeedInfoActivity.this) .load(feed.getImageUri()) + .fit() .into(imgvCover); } }); diff --git a/src/de/danoeh/antennapod/activity/MediaplayerActivity.java b/src/de/danoeh/antennapod/activity/MediaplayerActivity.java index 13e7b8a82..2e5372b60 100644 --- a/src/de/danoeh/antennapod/activity/MediaplayerActivity.java +++ b/src/de/danoeh/antennapod/activity/MediaplayerActivity.java @@ -502,18 +502,24 @@ public abstract class MediaplayerActivity extends ActionBarActivity @Override public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { - prog = controller.onSeekBarProgressChanged(seekBar, progress, fromUser, - txtvPosition); + if (controller != null) { + prog = controller.onSeekBarProgressChanged(seekBar, progress, fromUser, + txtvPosition); + } } @Override public void onStartTrackingTouch(SeekBar seekBar) { - controller.onSeekBarStartTrackingTouch(seekBar); + if (controller != null) { + controller.onSeekBarStartTrackingTouch(seekBar); + } } @Override public void onStopTrackingTouch(SeekBar seekBar) { - controller.onSeekBarStopTrackingTouch(seekBar, prog); + if (controller != null) { + controller.onSeekBarStopTrackingTouch(seekBar, prog); + } } } diff --git a/src/de/danoeh/antennapod/activity/PreferenceActivity.java b/src/de/danoeh/antennapod/activity/PreferenceActivity.java index cd6731c02..a21985bb8 100644 --- a/src/de/danoeh/antennapod/activity/PreferenceActivity.java +++ b/src/de/danoeh/antennapod/activity/PreferenceActivity.java @@ -9,6 +9,7 @@ import android.content.res.Resources.Theme; import android.net.wifi.WifiConfiguration; import android.net.wifi.WifiManager; import android.os.Bundle; +import android.os.Build; import android.preference.CheckBoxPreference; import android.preference.ListPreference; import android.preference.Preference; @@ -60,6 +61,9 @@ public class PreferenceActivity extends android.preference.PreferenceActivity { private static final String PREF_GPODNET_LOGOUT = "pref_gpodnet_logout"; private static final String PREF_GPODNET_HOSTNAME = "pref_gpodnet_hostname"; + private static final String PREF_EXPANDED_NOTIFICATION = "prefExpandNotify"; + private static final String PREF_PERSISTENT_NOTIFICATION = "prefPersistNotify"; + private CheckBoxPreference[] selectedNetworks; @SuppressLint("NewApi") @@ -77,6 +81,23 @@ public class PreferenceActivity extends android.preference.PreferenceActivity { } addPreferencesFromResource(R.xml.preferences); + + if (android.os.Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) { + // disable expanded notification option on unsupported android versions + findPreference(PREF_EXPANDED_NOTIFICATION).setEnabled(false); + findPreference(PREF_EXPANDED_NOTIFICATION).setOnPreferenceClickListener( + new OnPreferenceClickListener() { + + @Override + public boolean onPreferenceClick(Preference preference) { + Toast toast = Toast.makeText(PreferenceActivity.this, R.string.pref_expand_notify_unsupport_toast, Toast.LENGTH_SHORT); + toast.show(); + return true; + } + } + ); + } + findPreference(PREF_FLATTR_THIS_APP).setOnPreferenceClickListener( new OnPreferenceClickListener() { @@ -272,8 +293,6 @@ public class PreferenceActivity extends android.preference.PreferenceActivity { buildAutodownloadSelectedNetworsPreference(); setSelectedNetworksEnabled(UserPreferences .isEnableAutodownloadWifiFilter()); - - } private void updateGpodnetPreferenceScreen() { diff --git a/src/de/danoeh/antennapod/activity/gpoddernet/GpodnetAuthenticationActivity.java b/src/de/danoeh/antennapod/activity/gpoddernet/GpodnetAuthenticationActivity.java index cb6dc41cf..6a60f65fe 100644 --- a/src/de/danoeh/antennapod/activity/gpoddernet/GpodnetAuthenticationActivity.java +++ b/src/de/danoeh/antennapod/activity/gpoddernet/GpodnetAuthenticationActivity.java @@ -270,8 +270,10 @@ public class GpodnetAuthenticationActivity extends ActionBarActivity { @Override public void onClick(View v) { final int position = spinnerDevices.getSelectedItemPosition(); - selectedDevice = devices.get().get(position); - advance(); + if (position != AdapterView.INVALID_POSITION) { + selectedDevice = devices.get().get(position); + advance(); + } } }); } diff --git a/src/de/danoeh/antennapod/adapter/DownloadedEpisodesListAdapter.java b/src/de/danoeh/antennapod/adapter/DownloadedEpisodesListAdapter.java index 641a1368d..ef5af67de 100644 --- a/src/de/danoeh/antennapod/adapter/DownloadedEpisodesListAdapter.java +++ b/src/de/danoeh/antennapod/adapter/DownloadedEpisodesListAdapter.java @@ -89,7 +89,7 @@ public class DownloadedEpisodesListAdapter extends BaseAdapter { PicassoProvider.getMediaMetadataPicassoInstance(context) .load(item.getImageUri()) - .resize(imageSize, imageSize) + .fit() .into(holder.imageView); return convertView; diff --git a/src/de/danoeh/antennapod/adapter/ExternalEpisodesListAdapter.java b/src/de/danoeh/antennapod/adapter/ExternalEpisodesListAdapter.java index 56c3e1ca6..3f666eb8b 100644 --- a/src/de/danoeh/antennapod/adapter/ExternalEpisodesListAdapter.java +++ b/src/de/danoeh/antennapod/adapter/ExternalEpisodesListAdapter.java @@ -176,7 +176,7 @@ public class ExternalEpisodesListAdapter extends BaseExpandableListAdapter { PicassoProvider.getMediaMetadataPicassoInstance(context) .load(item.getImageUri()) - .resize(imageSize, imageSize) + .fit() .into(holder.feedImage); holder.butAction.setFocusable(false); diff --git a/src/de/danoeh/antennapod/adapter/NavListAdapter.java b/src/de/danoeh/antennapod/adapter/NavListAdapter.java index ed85c8836..ef8e8ce07 100644 --- a/src/de/danoeh/antennapod/adapter/NavListAdapter.java +++ b/src/de/danoeh/antennapod/adapter/NavListAdapter.java @@ -32,8 +32,6 @@ public class NavListAdapter extends BaseAdapter { private ItemAccess itemAccess; private Context context; - private final int imageSize; - public NavListAdapter(ItemAccess itemAccess, Context context) { this.itemAccess = itemAccess; this.context = context; @@ -43,7 +41,6 @@ public class NavListAdapter extends BaseAdapter { drawables = new Drawable[]{ta.getDrawable(0), ta.getDrawable(1), ta.getDrawable(2), ta.getDrawable(3), ta.getDrawable(4)}; ta.recycle(); - this.imageSize = (int) context.getResources().getDimension(R.dimen.thumbnail_length_navlist); } @Override @@ -195,7 +192,7 @@ public class NavListAdapter extends BaseAdapter { PicassoProvider.getDefaultPicassoInstance(context) .load(feed.getImageUri()) - .resize(imageSize, imageSize) + .fit() .into(holder.image); return convertView; diff --git a/src/de/danoeh/antennapod/adapter/NewEpisodesListAdapter.java b/src/de/danoeh/antennapod/adapter/NewEpisodesListAdapter.java index 4370de14d..8abe49133 100644 --- a/src/de/danoeh/antennapod/adapter/NewEpisodesListAdapter.java +++ b/src/de/danoeh/antennapod/adapter/NewEpisodesListAdapter.java @@ -27,7 +27,6 @@ public class NewEpisodesListAdapter extends BaseAdapter { private final ItemAccess itemAccess; private final ActionButtonCallback actionButtonCallback; private final ActionButtonUtils actionButtonUtils; - private final int imageSize; public NewEpisodesListAdapter(Context context, ItemAccess itemAccess, ActionButtonCallback actionButtonCallback) { super(); @@ -35,7 +34,6 @@ public class NewEpisodesListAdapter extends BaseAdapter { this.itemAccess = itemAccess; this.actionButtonUtils = new ActionButtonUtils(context); this.actionButtonCallback = actionButtonCallback; - this.imageSize = (int) context.getResources().getDimension(R.dimen.thumbnail_length_itemlist); } @Override @@ -133,7 +131,7 @@ public class NewEpisodesListAdapter extends BaseAdapter { PicassoProvider.getMediaMetadataPicassoInstance(context) .load(item.getImageUri()) - .resize(imageSize, imageSize) + .fit() .into(holder.imageView); return convertView; diff --git a/src/de/danoeh/antennapod/adapter/QueueListAdapter.java b/src/de/danoeh/antennapod/adapter/QueueListAdapter.java index c670089b9..ebe519592 100644 --- a/src/de/danoeh/antennapod/adapter/QueueListAdapter.java +++ b/src/de/danoeh/antennapod/adapter/QueueListAdapter.java @@ -22,7 +22,6 @@ public class QueueListAdapter extends BaseAdapter { private final ActionButtonCallback actionButtonCallback; private final ActionButtonUtils actionButtonUtils; - private final int imageSize; public QueueListAdapter(Context context, ItemAccess itemAccess, ActionButtonCallback actionButtonCallback) { super(); @@ -30,8 +29,6 @@ public class QueueListAdapter extends BaseAdapter { this.itemAccess = itemAccess; this.actionButtonUtils = new ActionButtonUtils(context); this.actionButtonCallback = actionButtonCallback; - this.imageSize = (int) context.getResources().getDimension(R.dimen.thumbnail_length_queue_item); - } @Override @@ -97,7 +94,7 @@ public class QueueListAdapter extends BaseAdapter { PicassoProvider.getMediaMetadataPicassoInstance(context) .load(item.getImageUri()) - .resize(imageSize, imageSize) + .fit() .into(holder.imageView); return convertView; diff --git a/src/de/danoeh/antennapod/adapter/SearchlistAdapter.java b/src/de/danoeh/antennapod/adapter/SearchlistAdapter.java index 6b1fefaad..2314c2269 100644 --- a/src/de/danoeh/antennapod/adapter/SearchlistAdapter.java +++ b/src/de/danoeh/antennapod/adapter/SearchlistAdapter.java @@ -23,12 +23,10 @@ public class SearchlistAdapter extends BaseAdapter { private final Context context; private final ItemAccess itemAccess; - private final int imageSize; public SearchlistAdapter(Context context, ItemAccess itemAccess) { this.context = context; this.itemAccess = itemAccess; - this.imageSize = (int) context.getResources().getDimension(R.dimen.thumbnail_length); } @Override @@ -76,7 +74,7 @@ public class SearchlistAdapter extends BaseAdapter { PicassoProvider.getDefaultPicassoInstance(context) .load(feed.getImageUri()) - .resize(imageSize, imageSize) + .fit() .into(holder.cover); } else if (component.getClass() == FeedItem.class) { @@ -89,7 +87,7 @@ public class SearchlistAdapter extends BaseAdapter { PicassoProvider.getDefaultPicassoInstance(context) .load(item.getFeed().getImageUri()) - .resize(imageSize, imageSize) + .fit() .into(holder.cover); } diff --git a/src/de/danoeh/antennapod/adapter/gpodnet/PodcastListAdapter.java b/src/de/danoeh/antennapod/adapter/gpodnet/PodcastListAdapter.java index dcad2d524..aeb1fc53a 100644 --- a/src/de/danoeh/antennapod/adapter/gpodnet/PodcastListAdapter.java +++ b/src/de/danoeh/antennapod/adapter/gpodnet/PodcastListAdapter.java @@ -8,6 +8,8 @@ import android.widget.ArrayAdapter; import android.widget.ImageView; import android.widget.TextView; +import org.apache.commons.lang3.StringUtils; + import java.util.List; import de.danoeh.antennapod.R; @@ -18,11 +20,9 @@ import de.danoeh.antennapod.gpoddernet.model.GpodnetPodcast; * Adapter for displaying a list of GPodnetPodcast-Objects. */ public class PodcastListAdapter extends ArrayAdapter<GpodnetPodcast> { - private final int thumbnailLength; public PodcastListAdapter(Context context, int resource, List<GpodnetPodcast> objects) { super(context, resource, objects); - thumbnailLength = (int) context.getResources().getDimension(R.dimen.thumbnail_length); } @Override @@ -50,10 +50,12 @@ public class PodcastListAdapter extends ArrayAdapter<GpodnetPodcast> { holder.title.setText(podcast.getTitle()); holder.description.setText(podcast.getDescription()); - PicassoProvider.getDefaultPicassoInstance(convertView.getContext()) - .load(podcast.getLogoUrl()) - .resize(thumbnailLength, thumbnailLength) - .into(holder.image); + if (StringUtils.isNoneBlank(podcast.getLogoUrl())) { + PicassoProvider.getDefaultPicassoInstance(convertView.getContext()) + .load(podcast.getLogoUrl()) + .fit() + .into(holder.image); + } return convertView; } diff --git a/src/de/danoeh/antennapod/asynctask/PicassoImageResource.java b/src/de/danoeh/antennapod/asynctask/PicassoImageResource.java index 84179cfcb..26f9d9278 100644 --- a/src/de/danoeh/antennapod/asynctask/PicassoImageResource.java +++ b/src/de/danoeh/antennapod/asynctask/PicassoImageResource.java @@ -18,8 +18,20 @@ public interface PicassoImageResource { */ public static final String SCHEME_MEDIA = "media"; + + /** + * Parameter key for an encoded fallback Uri. This Uri MUST point to a local image file + */ + public static final String PARAM_FALLBACK = "fallback"; + /** * Returns a Uri to the image or null if no image is available. + * <p/> + * The Uri can either be an HTTP-URL, a URL pointing to a local image file or + * a non-image file (see SCHEME_MEDIA for more details). + * <p/> + * The Uri can also have an optional fallback-URL if loading the default URL + * failed (see PARAM_FALLBACK). */ public Uri getImageUri(); } diff --git a/src/de/danoeh/antennapod/asynctask/PicassoProvider.java b/src/de/danoeh/antennapod/asynctask/PicassoProvider.java index 9ecf87023..849725630 100644 --- a/src/de/danoeh/antennapod/asynctask/PicassoProvider.java +++ b/src/de/danoeh/antennapod/asynctask/PicassoProvider.java @@ -127,14 +127,25 @@ public class PicassoProvider { mmr.setDataSource(uri.getPath()); byte[] data = mmr.getEmbeddedPicture(); mmr.release(); + if (data != null) { return new Response(new ByteArrayInputStream(data), true, data.length); } else { + + // check for fallback Uri + String fallbackParam = uri.getQueryParameter(PicassoImageResource.PARAM_FALLBACK); + + if (fallbackParam != null) { + String fallback = Uri.decode(Uri.parse(fallbackParam).getPath()); + if (fallback != null) { + File imageFile = new File(fallback); + return new Response(new BufferedInputStream(new FileInputStream(imageFile)), true, imageFile.length()); + } + } return null; } } } - return okHttpDownloader.load(uri, b); } } diff --git a/src/de/danoeh/antennapod/dialog/FeedItemDialog.java b/src/de/danoeh/antennapod/dialog/FeedItemDialog.java index 7384463de..4cd6a379e 100644 --- a/src/de/danoeh/antennapod/dialog/FeedItemDialog.java +++ b/src/de/danoeh/antennapod/dialog/FeedItemDialog.java @@ -11,6 +11,7 @@ import android.os.AsyncTask; import android.os.Build; import android.os.Bundle; import android.support.v7.widget.PopupMenu; +import android.text.TextUtils; import android.util.Log; import android.util.TypedValue; import android.view.MenuItem; @@ -115,6 +116,11 @@ public class FeedItemDialog extends Dialog { popupMenu = new PopupMenu(getContext(), butMore); webvDescription.setWebViewClient(new WebViewClient()); + + if (Build.VERSION.SDK_INT >= 14) { // ellipsize is causing problems on old versions, see #448 + txtvTitle.setEllipsize(TextUtils.TruncateAt.END); + } + txtvTitle.setText(item.getTitle()); if (UserPreferences.getTheme() == R.style.Theme_AntennaPod_Dark) { diff --git a/src/de/danoeh/antennapod/feed/FeedComponent.java b/src/de/danoeh/antennapod/feed/FeedComponent.java index 66a2f9cc5..48b243770 100644 --- a/src/de/danoeh/antennapod/feed/FeedComponent.java +++ b/src/de/danoeh/antennapod/feed/FeedComponent.java @@ -2,43 +2,43 @@ package de.danoeh.antennapod.feed; /** * Represents every possible component of a feed - * @author daniel * + * @author daniel */ public abstract class FeedComponent { - protected long id; - - public FeedComponent() { - super(); - } - - public long getId() { - return id; - } - - public void setId(long id) { - this.id = id; - } - - /** - * Update this FeedComponent's attributes with the attributes from another - * FeedComponent. This method should only update attributes which where read from - * the feed. - */ - public void updateFromOther(FeedComponent other) { - } - - /** - * Compare's this FeedComponent's attribute values with another FeedComponent's - * attribute values. This method will only compare attributes which were - * read from the feed. - * - * @return true if attribute values are different, false otherwise - */ - public boolean compareWithOther(FeedComponent other) { - return false; - } + protected long id; + + public FeedComponent() { + super(); + } + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + /** + * Update this FeedComponent's attributes with the attributes from another + * FeedComponent. This method should only update attributes which where read from + * the feed. + */ + public void updateFromOther(FeedComponent other) { + } + + /** + * Compare's this FeedComponent's attribute values with another FeedComponent's + * attribute values. This method will only compare attributes which were + * read from the feed. + * + * @return true if attribute values are different, false otherwise + */ + public boolean compareWithOther(FeedComponent other) { + return false; + } /** @@ -47,4 +47,20 @@ public abstract class FeedComponent { */ public abstract String getHumanReadableIdentifier(); + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + FeedComponent that = (FeedComponent) o; + + if (id != that.id) return false; + + return true; + } + + @Override + public int hashCode() { + return (int) (id ^ (id >>> 32)); + } }
\ No newline at end of file diff --git a/src/de/danoeh/antennapod/feed/FeedMedia.java b/src/de/danoeh/antennapod/feed/FeedMedia.java index f555654d0..9298ebe8a 100644 --- a/src/de/danoeh/antennapod/feed/FeedMedia.java +++ b/src/de/danoeh/antennapod/feed/FeedMedia.java @@ -386,9 +386,23 @@ public class FeedMedia extends FeedFile implements Playable { @Override public Uri getImageUri() { + final Uri feedImgUri = getFeedImageUri(); + if (localFileAvailable()) { - return new Uri.Builder().scheme(SCHEME_MEDIA).encodedPath(getLocalMediaUrl()).build(); - } else if (item != null && item.getFeed() != null) { + Uri.Builder builder = new Uri.Builder(); + builder.scheme(SCHEME_MEDIA) + .encodedPath(getLocalMediaUrl()); + if (feedImgUri != null) { + builder.appendQueryParameter(PARAM_FALLBACK, feedImgUri.toString()); + } + return builder.build(); + } else { + return feedImgUri; + } + } + + private Uri getFeedImageUri() { + if (item != null && item.getFeed() != null) { return item.getFeed().getImageUri(); } else { return null; diff --git a/src/de/danoeh/antennapod/fragment/ExternalPlayerFragment.java b/src/de/danoeh/antennapod/fragment/ExternalPlayerFragment.java index 77587194b..985673dd3 100644 --- a/src/de/danoeh/antennapod/fragment/ExternalPlayerFragment.java +++ b/src/de/danoeh/antennapod/fragment/ExternalPlayerFragment.java @@ -207,10 +207,9 @@ public class ExternalPlayerFragment extends Fragment { if (media != null) { txtvTitle.setText(media.getEpisodeTitle()); - int imageSize = (int) getResources().getDimension(R.dimen.external_player_height); PicassoProvider.getMediaMetadataPicassoInstance(getActivity()) .load(media.getImageUri()) - .resize(imageSize, imageSize) + .fit() .into(imgvCover); fragmentLayout.setVisibility(View.VISIBLE); diff --git a/src/de/danoeh/antennapod/fragment/ItemlistFragment.java b/src/de/danoeh/antennapod/fragment/ItemlistFragment.java index 5ef914f6c..909774467 100644 --- a/src/de/danoeh/antennapod/fragment/ItemlistFragment.java +++ b/src/de/danoeh/antennapod/fragment/ItemlistFragment.java @@ -350,10 +350,9 @@ public class ItemlistFragment extends ListFragment { txtvTitle.setText(feed.getTitle()); txtvAuthor.setText(feed.getAuthor()); - int imageSize = (int) getResources().getDimension(R.dimen.thumbnail_length_onlinefeedview); PicassoProvider.getDefaultPicassoInstance(getActivity()) .load(feed.getImageUri()) - .resize(imageSize, imageSize) + .fit() .into(imgvCover); if (feed.getLink() == null) { diff --git a/src/de/danoeh/antennapod/fragment/SearchFragment.java b/src/de/danoeh/antennapod/fragment/SearchFragment.java index b3ade4d70..b1411cf0a 100644 --- a/src/de/danoeh/antennapod/fragment/SearchFragment.java +++ b/src/de/danoeh/antennapod/fragment/SearchFragment.java @@ -160,7 +160,7 @@ public class SearchFragment extends ListFragment { private final EventDistributor.EventListener contentUpdate = new EventDistributor.EventListener() { @Override public void update(EventDistributor eventDistributor, Integer arg) { - if ((arg & (EventDistributor.DOWNLOAD_QUEUED)) != 0) { + if ((arg & (EventDistributor.DOWNLOAD_QUEUED)) != 0 && feedItemDialog != null) { feedItemDialog.updateMenuAppearance(); } if ((arg & (EventDistributor.UNREAD_ITEMS_UPDATE diff --git a/src/de/danoeh/antennapod/fragment/gpodnet/TagListFragment.java b/src/de/danoeh/antennapod/fragment/gpodnet/TagListFragment.java index 2289862aa..a7e1033df 100644 --- a/src/de/danoeh/antennapod/fragment/gpodnet/TagListFragment.java +++ b/src/de/danoeh/antennapod/fragment/gpodnet/TagListFragment.java @@ -1,5 +1,6 @@ package de.danoeh.antennapod.fragment.gpodnet; +import android.app.Activity; import android.content.Context; import android.os.AsyncTask; import android.os.Bundle; @@ -43,8 +44,11 @@ public class TagListFragment extends ListFragment { sv.setOnQueryTextListener(new SearchView.OnQueryTextListener() { @Override public boolean onQueryTextSubmit(String s) { - sv.clearFocus(); - ((MainActivity) getActivity()).loadChildFragment(SearchListFragment.newInstance(s)); + Activity activity = getActivity(); + if (activity != null) { + sv.clearFocus(); + ((MainActivity) activity).loadChildFragment(SearchListFragment.newInstance(s)); + } return true; } diff --git a/src/de/danoeh/antennapod/preferences/UserPreferences.java b/src/de/danoeh/antennapod/preferences/UserPreferences.java index 2020ddfae..73a4a1a14 100644 --- a/src/de/danoeh/antennapod/preferences/UserPreferences.java +++ b/src/de/danoeh/antennapod/preferences/UserPreferences.java @@ -6,6 +6,7 @@ import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.preference.PreferenceManager; +import android.support.v4.app.NotificationCompat; import android.util.Log; import org.apache.commons.lang3.StringUtils; @@ -53,6 +54,8 @@ public class UserPreferences implements private static final String PREF_PLAYBACK_SPEED_ARRAY = "prefPlaybackSpeedArray"; public static final String PREF_PAUSE_PLAYBACK_FOR_FOCUS_LOSS = "prefPauseForFocusLoss"; private static final String PREF_SEEK_DELTA_SECS = "prefSeekDeltaSecs"; + private static final String PREF_EXPANDED_NOTIFICATION = "prefExpandNotify"; + private static final String PREF_PERSISTENT_NOTIFICATION = "prefPersistNotify"; // TODO: Make this value configurable private static final float PREF_AUTO_FLATTR_PLAYED_DURATION_THRESHOLD_DEFAULT = 0.8f; @@ -82,6 +85,8 @@ public class UserPreferences implements private boolean pauseForFocusLoss; private int seekDeltaSecs; private boolean isFreshInstall; + private int notifyPriority; + private boolean persistNotify; private UserPreferences(Context context) { this.context = context; @@ -138,6 +143,13 @@ public class UserPreferences implements PREF_PLAYBACK_SPEED_ARRAY, null)); pauseForFocusLoss = sp.getBoolean(PREF_PAUSE_PLAYBACK_FOR_FOCUS_LOSS, false); seekDeltaSecs = Integer.valueOf(sp.getString(PREF_SEEK_DELTA_SECS, "30")); + if (sp.getBoolean(PREF_EXPANDED_NOTIFICATION, false)) { + notifyPriority = NotificationCompat.PRIORITY_MAX; + } + else { + notifyPriority = NotificationCompat.PRIORITY_DEFAULT; + } + persistNotify = sp.getBoolean(PREF_PERSISTENT_NOTIFICATION, false); } private int readThemeValue(String valueFromPrefs) { @@ -243,6 +255,17 @@ public class UserPreferences implements return instance.autoFlattr; } + public static int getNotifyPriority() { + instanceAvailable(); + return instance.notifyPriority; + } + + public static boolean isPersistNotify() { + instanceAvailable(); + return instance.persistNotify; + } + + /** * Returns the time after which an episode should be auto-flattr'd in percent of the episode's * duration. @@ -366,6 +389,15 @@ public class UserPreferences implements } else if (key.equals(PREF_AUTO_FLATTR_PLAYED_DURATION_THRESHOLD)) { autoFlattrPlayedDurationThreshold = sp.getFloat(PREF_AUTO_FLATTR_PLAYED_DURATION_THRESHOLD, PREF_AUTO_FLATTR_PLAYED_DURATION_THRESHOLD_DEFAULT); + } else if (key.equals(PREF_EXPANDED_NOTIFICATION)) { + if (sp.getBoolean(PREF_EXPANDED_NOTIFICATION, false)) { + notifyPriority = NotificationCompat.PRIORITY_MAX; + } + else { + notifyPriority = NotificationCompat.PRIORITY_DEFAULT; + } + } else if (key.equals(PREF_PERSISTENT_NOTIFICATION)) { + persistNotify = sp.getBoolean(PREF_PERSISTENT_NOTIFICATION, false); } } diff --git a/src/de/danoeh/antennapod/service/playback/PlaybackService.java b/src/de/danoeh/antennapod/service/playback/PlaybackService.java index 59d7ddbb9..6c292c08a 100644 --- a/src/de/danoeh/antennapod/service/playback/PlaybackService.java +++ b/src/de/danoeh/antennapod/service/playback/PlaybackService.java @@ -297,7 +297,12 @@ public class PlaybackService extends Service { case KeyEvent.KEYCODE_HEADSETHOOK: case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE: if (status == PlayerStatus.PLAYING) { - mediaPlayer.pause(true, true); + if (UserPreferences.isPersistNotify()) { + mediaPlayer.pause(false, true); + } + else { + mediaPlayer.pause(true, true); + } } else if (status == PlayerStatus.PAUSED || status == PlayerStatus.PREPARED) { mediaPlayer.resume(); } else if (status == PlayerStatus.PREPARING) { @@ -317,7 +322,12 @@ public class PlaybackService extends Service { break; case KeyEvent.KEYCODE_MEDIA_PAUSE: if (status == PlayerStatus.PLAYING) { + if (UserPreferences.isPersistNotify()) { + mediaPlayer.pause(false, true); + } + else { mediaPlayer.pause(true, true); + } } break; case KeyEvent.KEYCODE_MEDIA_NEXT: @@ -328,6 +338,12 @@ public class PlaybackService extends Service { case KeyEvent.KEYCODE_MEDIA_REWIND: mediaPlayer.seekDelta(-UserPreferences.getSeekDeltaMs()); break; + case KeyEvent.KEYCODE_MEDIA_STOP: + if (status == PlayerStatus.PLAYING) { + mediaPlayer.pause(true, true); + } + stopForeground(true); // gets rid of persistent notification + break; default: if (info.playable != null && info.playerStatus == PlayerStatus.PLAYING) { // only notify the user about an unknown key event if it is actually doing something String message = String.format(getResources().getString(R.string.unknown_media_key), keycode); @@ -401,7 +417,13 @@ public class PlaybackService extends Service { taskManager.cancelPositionSaver(); saveCurrentPosition(false, 0); taskManager.cancelWidgetUpdater(); - stopForeground(true); + if (UserPreferences.isPersistNotify() && android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { + // do not remove notification on pause based on user pref and whether android version supports expanded notifications + } + else { + // remove notifcation on pause + stopForeground(true); + } break; case STOPPED: @@ -713,7 +735,7 @@ public class PlaybackService extends Service { String contentTitle = info.playable.getEpisodeTitle(); Notification notification = null; if (android.os.Build.VERSION.SDK_INT >= 16) { - Intent pauseButtonIntent = new Intent( + Intent pauseButtonIntent = new Intent( // pause button intent PlaybackService.this, PlaybackService.class); pauseButtonIntent.putExtra( MediaButtonReceiver.EXTRA_KEYCODE, @@ -722,6 +744,24 @@ public class PlaybackService extends Service { .getService(PlaybackService.this, 0, pauseButtonIntent, PendingIntent.FLAG_UPDATE_CURRENT); + Intent playButtonIntent = new Intent( // play button intent + PlaybackService.this, PlaybackService.class); + playButtonIntent.putExtra( + MediaButtonReceiver.EXTRA_KEYCODE, + KeyEvent.KEYCODE_MEDIA_PLAY); + PendingIntent playButtonPendingIntent = PendingIntent + .getService(PlaybackService.this, 1, + playButtonIntent, + PendingIntent.FLAG_UPDATE_CURRENT); + Intent stopButtonIntent = new Intent( // stop button intent + PlaybackService.this, PlaybackService.class); + stopButtonIntent.putExtra( + MediaButtonReceiver.EXTRA_KEYCODE, + KeyEvent.KEYCODE_MEDIA_STOP); + PendingIntent stopButtonPendingIntent = PendingIntent + .getService(PlaybackService.this, 2, + stopButtonIntent, + PendingIntent.FLAG_UPDATE_CURRENT); Notification.Builder notificationBuilder = new Notification.Builder( PlaybackService.this) .setContentTitle(contentTitle) @@ -730,9 +770,16 @@ public class PlaybackService extends Service { .setContentIntent(pIntent) .setLargeIcon(icon) .setSmallIcon(R.drawable.ic_stat_antenna) - .addAction(android.R.drawable.ic_media_pause, + .setPriority(UserPreferences.getNotifyPriority()) // set notification priority + .addAction(android.R.drawable.ic_media_play, //play action + getString(R.string.play_label), + playButtonPendingIntent) + .addAction(android.R.drawable.ic_media_pause, //pause action getString(R.string.pause_label), - pauseButtonPendingIntent); + pauseButtonPendingIntent) + .addAction(android.R.drawable.ic_menu_close_clear_cancel, // stop action + getString(R.string.stop_label), + stopButtonPendingIntent); notification = notificationBuilder.build(); } else { NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder( @@ -949,7 +996,12 @@ public class PlaybackService extends Service { */ private void pauseIfPauseOnDisconnect() { if (UserPreferences.isPauseOnHeadsetDisconnect()) { + if (UserPreferences.isPersistNotify()) { + mediaPlayer.pause(false, true); + } + else { mediaPlayer.pause(true, true); + } } } diff --git a/src/de/danoeh/antennapod/service/playback/PlaybackServiceMediaPlayer.java b/src/de/danoeh/antennapod/service/playback/PlaybackServiceMediaPlayer.java index 49f20012d..9978fff3c 100644 --- a/src/de/danoeh/antennapod/service/playback/PlaybackServiceMediaPlayer.java +++ b/src/de/danoeh/antennapod/service/playback/PlaybackServiceMediaPlayer.java @@ -747,8 +747,8 @@ public class PlaybackServiceMediaPlayer { pause(false, false); pausedBecauseOfTransientAudiofocusLoss = true; } - playerLock.unlock(); } + playerLock.unlock(); } }); } diff --git a/src/de/danoeh/antennapod/service/playback/PlayerWidgetService.java b/src/de/danoeh/antennapod/service/playback/PlayerWidgetService.java index 71bc40c2a..ec28724ed 100644 --- a/src/de/danoeh/antennapod/service/playback/PlayerWidgetService.java +++ b/src/de/danoeh/antennapod/service/playback/PlayerWidgetService.java @@ -24,6 +24,10 @@ public class PlayerWidgetService extends Service { private static final String TAG = "PlayerWidgetService"; private PlaybackService playbackService; + + /** Controls write access to playbackservice reference */ + private Object psLock; + /** True while service is updating the widget */ private volatile boolean isUpdating; @@ -36,6 +40,7 @@ public class PlayerWidgetService extends Service { if (BuildConfig.DEBUG) Log.d(TAG, "Service created"); isUpdating = false; + psLock = new Object(); } @Override @@ -148,16 +153,20 @@ public class PlayerWidgetService extends Service { public void onServiceConnected(ComponentName className, IBinder service) { if (BuildConfig.DEBUG) Log.d(TAG, "Connection to service established"); - playbackService = ((PlaybackService.LocalBinder) service) - .getService(); - startViewUpdaterIfNotRunning(); + synchronized (psLock) { + playbackService = ((PlaybackService.LocalBinder) service) + .getService(); + startViewUpdaterIfNotRunning(); + } } @Override public void onServiceDisconnected(ComponentName name) { - playbackService = null; - if (BuildConfig.DEBUG) - Log.d(TAG, "Disconnected from service"); + synchronized (psLock) { + playbackService = null; + if (BuildConfig.DEBUG) + Log.d(TAG, "Disconnected from service"); + } } }; @@ -169,7 +178,7 @@ public class PlayerWidgetService extends Service { } } - static class ViewUpdater extends Thread { + class ViewUpdater extends Thread { private static final String THREAD_NAME = "ViewUpdater"; private PlayerWidgetService service; @@ -182,7 +191,9 @@ public class PlayerWidgetService extends Service { @Override public void run() { - service.updateViews(); + synchronized (psLock) { + service.updateViews(); + } } } diff --git a/src/de/danoeh/antennapod/storage/DownloadRequester.java b/src/de/danoeh/antennapod/storage/DownloadRequester.java index d305c572b..0eae52137 100644 --- a/src/de/danoeh/antennapod/storage/DownloadRequester.java +++ b/src/de/danoeh/antennapod/storage/DownloadRequester.java @@ -34,7 +34,7 @@ public class DownloadRequester { private static DownloadRequester downloader; - Map<String, DownloadRequest> downloads; + private Map<String, DownloadRequest> downloads; private DownloadRequester() { downloads = new ConcurrentHashMap<String, DownloadRequest>(); @@ -57,7 +57,7 @@ public class DownloadRequester { * call will return false. * @return True if the download request was accepted, false otherwise. */ - public boolean download(Context context, DownloadRequest request) { + public synchronized boolean download(Context context, DownloadRequest request) { Validate.notNull(context); Validate.notNull(request); @@ -145,7 +145,7 @@ public class DownloadRequester { return true; } - public void downloadFeed(Context context, Feed feed) + public synchronized void downloadFeed(Context context, Feed feed) throws DownloadRequestException { if (feedFileValid(feed)) { String username = (feed.getPreferences() != null) ? feed.getPreferences().getUsername() : null; @@ -156,7 +156,7 @@ public class DownloadRequester { } } - public void downloadImage(Context context, FeedImage image) + public synchronized void downloadImage(Context context, FeedImage image) throws DownloadRequestException { if (feedFileValid(image)) { download(context, image, new File(getImagefilePath(context), @@ -164,7 +164,7 @@ public class DownloadRequester { } } - public void downloadMedia(Context context, FeedMedia feedmedia) + public synchronized void downloadMedia(Context context, FeedMedia feedmedia) throws DownloadRequestException { if (feedFileValid(feedmedia)) { Feed feed = feedmedia.getItem().getFeed(); @@ -210,14 +210,14 @@ public class DownloadRequester { /** * Cancels a running download. */ - public void cancelDownload(final Context context, final FeedFile f) { + public synchronized 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) { + public synchronized void cancelDownload(final Context context, final String downloadUrl) { if (BuildConfig.DEBUG) Log.d(TAG, "Cancelling download with url " + downloadUrl); Intent cancelIntent = new Intent(DownloadService.ACTION_CANCEL_DOWNLOAD); @@ -228,7 +228,7 @@ public class DownloadRequester { /** * Cancels all running downloads */ - public void cancelAllDownloads(Context context) { + public synchronized void cancelAllDownloads(Context context) { if (BuildConfig.DEBUG) Log.d(TAG, "Cancelling all running downloads"); context.sendBroadcast(new Intent( @@ -238,7 +238,7 @@ public class DownloadRequester { /** * Returns true if there is at least one Feed in the downloads queue. */ - public boolean isDownloadingFeeds() { + public synchronized boolean isDownloadingFeeds() { for (DownloadRequest r : downloads.values()) { if (r.getFeedfileType() == Feed.FEEDFILETYPE_FEED) { return true; @@ -250,32 +250,32 @@ public class DownloadRequester { /** * Checks if feedfile is in the downloads list */ - public boolean isDownloadingFile(FeedFile item) { + public synchronized boolean isDownloadingFile(FeedFile item) { if (item.getDownload_url() != null) { return downloads.containsKey(item.getDownload_url()); } return false; } - public DownloadRequest getDownload(String downloadUrl) { + public synchronized 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) { + public synchronized boolean isDownloadingFile(String downloadUrl) { return downloads.get(downloadUrl) != null; } - public boolean hasNoDownloads() { + public synchronized boolean hasNoDownloads() { return downloads.isEmpty(); } /** * Remove an object from the downloads-list of the requester. */ - public void removeDownload(DownloadRequest r) { + public synchronized void removeDownload(DownloadRequest r) { if (downloads.remove(r.getSource()) == null) { Log.e(TAG, "Could not remove object with url " + r.getSource()); @@ -285,17 +285,17 @@ public class DownloadRequester { /** * Get the number of uncompleted Downloads */ - public int getNumberOfDownloads() { + public synchronized int getNumberOfDownloads() { return downloads.size(); } - public String getFeedfilePath(Context context) + public synchronized String getFeedfilePath(Context context) throws DownloadRequestException { return getExternalFilesDirOrThrowException(context, FEED_DOWNLOADPATH) .toString() + "/"; } - public String getFeedfileName(Feed feed) { + public synchronized String getFeedfileName(Feed feed) { String filename = feed.getDownload_url(); if (feed.getTitle() != null && !feed.getTitle().isEmpty()) { filename = feed.getTitle(); @@ -303,13 +303,13 @@ public class DownloadRequester { return "feed-" + FileNameGenerator.generateFileName(filename); } - public String getImagefilePath(Context context) + public synchronized String getImagefilePath(Context context) throws DownloadRequestException { return getExternalFilesDirOrThrowException(context, IMAGE_DOWNLOADPATH) .toString() + "/"; } - public String getImagefileName(FeedImage image) { + public synchronized String getImagefileName(FeedImage image) { String filename = image.getDownload_url(); if (image.getOwner() != null && image.getOwner().getHumanReadableIdentifier() != null) { filename = image.getOwner().getHumanReadableIdentifier(); @@ -317,7 +317,7 @@ public class DownloadRequester { return "image-" + FileNameGenerator.generateFileName(filename); } - public String getMediafilePath(Context context, FeedMedia media) + public synchronized String getMediafilePath(Context context, FeedMedia media) throws DownloadRequestException { File externalStorage = getExternalFilesDirOrThrowException( context, @@ -338,7 +338,7 @@ public class DownloadRequester { return result; } - public String getMediafilename(FeedMedia media) { + private String getMediafilename(FeedMedia media) { String filename; String titleBaseFilename = ""; diff --git a/src/de/danoeh/antennapod/syndication/namespace/NSSimpleChapters.java b/src/de/danoeh/antennapod/syndication/namespace/NSSimpleChapters.java index 3f983ee88..b45793b6b 100644 --- a/src/de/danoeh/antennapod/syndication/namespace/NSSimpleChapters.java +++ b/src/de/danoeh/antennapod/syndication/namespace/NSSimpleChapters.java @@ -1,5 +1,8 @@ package de.danoeh.antennapod.syndication.namespace; +import android.util.Log; + +import de.danoeh.antennapod.BuildConfig; import de.danoeh.antennapod.feed.Chapter; import de.danoeh.antennapod.feed.SimpleChapter; import de.danoeh.antennapod.syndication.handler.HandlerState; @@ -9,6 +12,8 @@ import org.xml.sax.Attributes; import java.util.ArrayList; public class NSSimpleChapters extends Namespace { + private static final String TAG = "NSSimpleChapters"; + public static final String NSTAG = "psc|sc"; public static final String NSURI = "http://podlove.org/simple-chapters"; @@ -24,12 +29,16 @@ public class NSSimpleChapters extends Namespace { if (localName.equals(CHAPTERS)) { state.getCurrentItem().setChapters(new ArrayList<Chapter>()); } else if (localName.equals(CHAPTER)) { - state.getCurrentItem() - .getChapters() - .add(new SimpleChapter(SyndDateUtils - .parseTimeString(attributes.getValue(START)), - attributes.getValue(TITLE), state.getCurrentItem(), - attributes.getValue(HREF))); + try { + state.getCurrentItem() + .getChapters() + .add(new SimpleChapter(SyndDateUtils + .parseTimeString(attributes.getValue(START)), + attributes.getValue(TITLE), state.getCurrentItem(), + attributes.getValue(HREF))); + } catch (NumberFormatException e) { + if (BuildConfig.DEBUG) Log.w(TAG, "Unable to read chapter", e); + } } return new SyndElement(localName, this); diff --git a/src/de/danoeh/antennapod/syndication/util/SyndDateUtils.java b/src/de/danoeh/antennapod/syndication/util/SyndDateUtils.java index 3138f087a..56687ac2e 100644 --- a/src/de/danoeh/antennapod/syndication/util/SyndDateUtils.java +++ b/src/de/danoeh/antennapod/syndication/util/SyndDateUtils.java @@ -7,130 +7,138 @@ import java.text.SimpleDateFormat; import java.util.Date; import java.util.Locale; -/** Parses several date formats. */ +/** + * Parses several date formats. + */ public class SyndDateUtils { - private static final String TAG = "DateUtils"; - - 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'"; - - /** RFC 3339 date format for localtime dates with offset. */ - public static final String RFC3339LOCAL = "yyyy-MM-dd'T'HH:mm:ssZ"; - - private static ThreadLocal<SimpleDateFormat> RFC822Formatter = new ThreadLocal<SimpleDateFormat>() { - @Override - protected SimpleDateFormat initialValue() { - return new SimpleDateFormat(RFC822DATES[0], Locale.US); - } - - }; - - private static ThreadLocal<SimpleDateFormat> RFC3339Formatter = new ThreadLocal<SimpleDateFormat>() { - @Override - protected SimpleDateFormat initialValue() { - return new SimpleDateFormat(RFC3339UTC, Locale.US); - } - - }; - - public static Date parseRFC822Date(String date) { - Date result = null; - if (date.contains("PDT")) { - date = date.replace("PDT", "PST8PDT"); - } - if (date.contains(",")) { - // Remove day of the week - date = date.substring(date.indexOf(",") + 1).trim(); - } - SimpleDateFormat format = RFC822Formatter.get(); - for (int i = 0; i < RFC822DATES.length; i++) { - try { - format.applyPattern(RFC822DATES[i]); - result = format.parse(date); - break; - } catch (ParseException e) { - e.printStackTrace(); - } - } - if (result == null) { - Log.e(TAG, "Unable to parse feed date correctly"); - } - - return result; - } - - public static Date parseRFC3339Date(String date) { - Date result = null; - SimpleDateFormat format = RFC3339Formatter.get(); - boolean isLocal = date.endsWith("Z"); - if (date.contains(".")) { - // remove secfrac - int fracIndex = date.indexOf("."); - String first = date.substring(0, fracIndex); - String second = null; - if (isLocal) { - second = date.substring(date.length() - 1); - } else { - if (date.contains("+")) { - second = date.substring(date.indexOf("+")); - } else { - second = date.substring(date.indexOf("-")); - } - } - - date = first + second; - } - if (isLocal) { - try { - result = format.parse(date); - } catch (ParseException e) { - e.printStackTrace(); - } - } else { - format.applyPattern(RFC3339LOCAL); - // remove last colon - StringBuffer buf = new StringBuffer(date.length() - 1); - int colonIdx = date.lastIndexOf(':'); - for (int x = 0; x < date.length(); x++) { - if (x != colonIdx) - buf.append(date.charAt(x)); - } - String bufStr = buf.toString(); - try { - result = format.parse(bufStr); - } catch (ParseException e) { - e.printStackTrace(); - Log.e(TAG, "Unable to parse date"); - } finally { - format.applyPattern(RFC3339UTC); - } - - } - - return result; - - } - - /** - * Takes a string of the form [HH:]MM:SS[.mmm] and converts it to - * milliseconds. - */ - public static long parseTimeString(final String time) { - String[] parts = time.split(":"); - long result = 0; - int idx = 0; - if (parts.length == 3) { - // string has hours - result += Integer.valueOf(parts[idx]) * 3600000L; - idx++; - } - result += Integer.valueOf(parts[idx]) * 60000L; - idx++; - result += (Float.valueOf(parts[idx])) * 1000L; - return result; - } + private static final String TAG = "DateUtils"; + + 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'"; + + /** + * RFC 3339 date format for localtime dates with offset. + */ + public static final String RFC3339LOCAL = "yyyy-MM-dd'T'HH:mm:ssZ"; + + private static ThreadLocal<SimpleDateFormat> RFC822Formatter = new ThreadLocal<SimpleDateFormat>() { + @Override + protected SimpleDateFormat initialValue() { + return new SimpleDateFormat(RFC822DATES[0], Locale.US); + } + + }; + + private static ThreadLocal<SimpleDateFormat> RFC3339Formatter = new ThreadLocal<SimpleDateFormat>() { + @Override + protected SimpleDateFormat initialValue() { + return new SimpleDateFormat(RFC3339UTC, Locale.US); + } + + }; + + public static Date parseRFC822Date(String date) { + Date result = null; + if (date.contains("PDT")) { + date = date.replace("PDT", "PST8PDT"); + } + if (date.contains(",")) { + // Remove day of the week + date = date.substring(date.indexOf(",") + 1).trim(); + } + SimpleDateFormat format = RFC822Formatter.get(); + for (int i = 0; i < RFC822DATES.length; i++) { + try { + format.applyPattern(RFC822DATES[i]); + result = format.parse(date); + break; + } catch (ParseException e) { + e.printStackTrace(); + } + } + if (result == null) { + Log.e(TAG, "Unable to parse feed date correctly"); + } + + return result; + } + + public static Date parseRFC3339Date(String date) { + Date result = null; + SimpleDateFormat format = RFC3339Formatter.get(); + boolean isLocal = date.endsWith("Z"); + if (date.contains(".")) { + // remove secfrac + int fracIndex = date.indexOf("."); + String first = date.substring(0, fracIndex); + String second = null; + if (isLocal) { + second = date.substring(date.length() - 1); + } else { + if (date.contains("+")) { + second = date.substring(date.indexOf("+")); + } else { + second = date.substring(date.indexOf("-")); + } + } + + date = first + second; + } + if (isLocal) { + try { + result = format.parse(date); + } catch (ParseException e) { + e.printStackTrace(); + } + } else { + format.applyPattern(RFC3339LOCAL); + // remove last colon + StringBuffer buf = new StringBuffer(date.length() - 1); + int colonIdx = date.lastIndexOf(':'); + for (int x = 0; x < date.length(); x++) { + if (x != colonIdx) + buf.append(date.charAt(x)); + } + String bufStr = buf.toString(); + try { + result = format.parse(bufStr); + } catch (ParseException e) { + e.printStackTrace(); + Log.e(TAG, "Unable to parse date"); + } finally { + format.applyPattern(RFC3339UTC); + } + + } + + return result; + + } + + /** + * Takes a string of the form [HH:]MM:SS[.mmm] and converts it to + * milliseconds. + * + * @throws java.lang.NumberFormatException if the number segments contain invalid numbers. + */ + public static long parseTimeString(final String time) { + String[] parts = time.split(":"); + long result = 0; + int idx = 0; + if (parts.length == 3) { + // string has hours + result += Integer.valueOf(parts[idx]) * 3600000L; + idx++; + } + result += Integer.valueOf(parts[idx]) * 60000L; + idx++; + result += (Float.valueOf(parts[idx])) * 1000L; + return result; + } public static String formatRFC822Date(Date date) { SimpleDateFormat format = RFC822Formatter.get(); diff --git a/src/de/danoeh/antennapod/util/ChapterUtils.java b/src/de/danoeh/antennapod/util/ChapterUtils.java index 9e1c50674..2d9022eed 100644 --- a/src/de/danoeh/antennapod/util/ChapterUtils.java +++ b/src/de/danoeh/antennapod/util/ChapterUtils.java @@ -1,6 +1,20 @@ package de.danoeh.antennapod.util; import android.util.Log; + +import org.apache.commons.io.IOUtils; + +import java.io.BufferedInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.Collections; +import java.util.List; + import de.danoeh.antennapod.BuildConfig; import de.danoeh.antennapod.feed.Chapter; import de.danoeh.antennapod.util.comparator.ChapterStartTimeComparator; @@ -9,253 +23,252 @@ import de.danoeh.antennapod.util.id3reader.ID3ReaderException; import de.danoeh.antennapod.util.playback.Playable; import de.danoeh.antennapod.util.vorbiscommentreader.VorbisCommentChapterReader; import de.danoeh.antennapod.util.vorbiscommentreader.VorbisCommentReaderException; -import org.apache.commons.io.IOUtils; -import java.io.*; -import java.net.MalformedURLException; -import java.net.URL; -import java.util.Collections; -import java.util.List; - -/** Utility class for getting chapter data from media files. */ +/** + * Utility class for getting chapter data from media files. + */ public class ChapterUtils { - private static final String TAG = "ChapterUtils"; + private static final String TAG = "ChapterUtils"; - private ChapterUtils() { - } + private ChapterUtils() { + } - /** - * Uses the download URL of a media object of a feeditem to read its ID3 - * chapters. - */ - public static void readID3ChaptersFromPlayableStreamUrl(Playable p) { - if (p != null && p.getStreamUrl() != null) { + /** + * Uses the download URL of a media object of a feeditem to read its ID3 + * chapters. + */ + public static void readID3ChaptersFromPlayableStreamUrl(Playable p) { + if (p != null && p.getStreamUrl() != null) { if (BuildConfig.DEBUG) Log.d(TAG, "Reading id3 chapters from item " + p.getEpisodeTitle()); - InputStream in = null; - try { - URL url = new URL(p.getStreamUrl()); - ChapterReader reader = new ChapterReader(); + InputStream in = null; + try { + URL url = new URL(p.getStreamUrl()); + ChapterReader reader = new ChapterReader(); - in = url.openStream(); - reader.readInputStream(in); - List<Chapter> chapters = reader.getChapters(); + in = url.openStream(); + reader.readInputStream(in); + List<Chapter> chapters = reader.getChapters(); - if (chapters != null) { - Collections - .sort(chapters, new ChapterStartTimeComparator()); - processChapters(chapters, p); - if (chaptersValid(chapters)) { - p.setChapters(chapters); - Log.i(TAG, "Chapters loaded"); - } else { - Log.e(TAG, "Chapter data was invalid"); - } - } else { - Log.i(TAG, "ChapterReader could not find any ID3 chapters"); - } - } catch (MalformedURLException e) { - e.printStackTrace(); - } catch (IOException e) { - e.printStackTrace(); - } catch (ID3ReaderException e) { - e.printStackTrace(); - } finally { - if (in != null) { - try { - in.close(); - } catch (IOException e) { - e.printStackTrace(); - } - } - } - } else { - Log.e(TAG, - "Unable to read ID3 chapters: media or download URL was null"); - } - } + if (chapters != null) { + Collections + .sort(chapters, new ChapterStartTimeComparator()); + processChapters(chapters, p); + if (chaptersValid(chapters)) { + p.setChapters(chapters); + Log.i(TAG, "Chapters loaded"); + } else { + Log.e(TAG, "Chapter data was invalid"); + } + } else { + Log.i(TAG, "ChapterReader could not find any ID3 chapters"); + } + } catch (MalformedURLException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } catch (ID3ReaderException e) { + e.printStackTrace(); + } finally { + if (in != null) { + try { + in.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + } else { + Log.e(TAG, + "Unable to read ID3 chapters: media or download URL was null"); + } + } - /** - * Uses the file URL of a media object of a feeditem to read its ID3 - * chapters. - */ - public static void readID3ChaptersFromPlayableFileUrl(Playable p) { - if (p != null && p.localFileAvailable() && p.getLocalMediaUrl() != null) { + /** + * Uses the file URL of a media object of a feeditem to read its ID3 + * chapters. + */ + public static void readID3ChaptersFromPlayableFileUrl(Playable p) { + if (p != null && p.localFileAvailable() && p.getLocalMediaUrl() != null) { if (BuildConfig.DEBUG) Log.d(TAG, "Reading id3 chapters from item " + p.getEpisodeTitle()); - File source = new File(p.getLocalMediaUrl()); - if (source.exists()) { - ChapterReader reader = new ChapterReader(); - InputStream in = null; + File source = new File(p.getLocalMediaUrl()); + if (source.exists()) { + ChapterReader reader = new ChapterReader(); + InputStream in = null; + + try { + in = new BufferedInputStream(new FileInputStream(source)); + reader.readInputStream(in); + List<Chapter> chapters = reader.getChapters(); - try { - in = new BufferedInputStream(new FileInputStream(source)); - reader.readInputStream(in); - List<Chapter> chapters = reader.getChapters(); + if (chapters != null) { + Collections.sort(chapters, + new ChapterStartTimeComparator()); + processChapters(chapters, p); + if (chaptersValid(chapters)) { + p.setChapters(chapters); + Log.i(TAG, "Chapters loaded"); + } else { + Log.e(TAG, "Chapter data was invalid"); + } + } else { + Log.i(TAG, + "ChapterReader could not find any ID3 chapters"); + } + } catch (IOException e) { + e.printStackTrace(); + } catch (ID3ReaderException e) { + e.printStackTrace(); + } finally { + if (in != null) { + try { + in.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + } else { + Log.e(TAG, "Unable to read id3 chapters: Source doesn't exist"); + } + } + } - if (chapters != null) { - Collections.sort(chapters, - new ChapterStartTimeComparator()); - processChapters(chapters, p); - if (chaptersValid(chapters)) { - p.setChapters(chapters); - Log.i(TAG, "Chapters loaded"); - } else { - Log.e(TAG, "Chapter data was invalid"); - } - } else { - Log.i(TAG, - "ChapterReader could not find any ID3 chapters"); - } - } catch (IOException e) { - e.printStackTrace(); - } catch (ID3ReaderException e) { - e.printStackTrace(); - } finally { - if (in != null) { - try { - in.close(); - } catch (IOException e) { - e.printStackTrace(); - } - } - } - } else { - Log.e(TAG, "Unable to read id3 chapters: Source doesn't exist"); - } - } - } + public static void readOggChaptersFromPlayableStreamUrl(Playable media) { + if (media != null && media.streamAvailable()) { + InputStream input = null; + try { + URL url = new URL(media.getStreamUrl()); + input = url.openStream(); + if (input != null) { + readOggChaptersFromInputStream(media, input); + } + } catch (MalformedURLException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } finally { + IOUtils.closeQuietly(input); + } + } + } - public static void readOggChaptersFromPlayableStreamUrl(Playable media) { - if (media != null && media.streamAvailable()) { - InputStream input = null; - try { - URL url = new URL(media.getStreamUrl()); - input = url.openStream(); - if (input != null) { - readOggChaptersFromInputStream(media, input); - } - } catch (MalformedURLException e) { - e.printStackTrace(); - } catch (IOException e) { - e.printStackTrace(); - } finally { - IOUtils.closeQuietly(input); - } - } - } + public static void readOggChaptersFromPlayableFileUrl(Playable media) { + if (media != null && media.getLocalMediaUrl() != null) { + File source = new File(media.getLocalMediaUrl()); + if (source.exists()) { + InputStream input = null; + try { + input = new BufferedInputStream(new FileInputStream(source)); + readOggChaptersFromInputStream(media, input); + } catch (FileNotFoundException e) { + e.printStackTrace(); + } finally { + IOUtils.closeQuietly(input); + } + } + } + } - public static void readOggChaptersFromPlayableFileUrl(Playable media) { - if (media != null && media.getLocalMediaUrl() != null) { - File source = new File(media.getLocalMediaUrl()); - if (source.exists()) { - InputStream input = null; - try { - input = new BufferedInputStream(new FileInputStream(source)); - readOggChaptersFromInputStream(media, input); - } catch (FileNotFoundException e) { - e.printStackTrace(); - } finally { - IOUtils.closeQuietly(input); - } - } - } - } + private static void readOggChaptersFromInputStream(Playable p, + InputStream input) { + if (BuildConfig.DEBUG) + Log.d(TAG, + "Trying to read chapters from item with title " + + p.getEpisodeTitle()); + try { + VorbisCommentChapterReader reader = new VorbisCommentChapterReader(); + reader.readInputStream(input); + List<Chapter> chapters = reader.getChapters(); + if (chapters != null) { + Collections.sort(chapters, new ChapterStartTimeComparator()); + processChapters(chapters, p); + if (chaptersValid(chapters)) { + p.setChapters(chapters); + Log.i(TAG, "Chapters loaded"); + } else { + Log.e(TAG, "Chapter data was invalid"); + } + } else { + Log.i(TAG, + "ChapterReader could not find any Ogg vorbis chapters"); + } + } catch (VorbisCommentReaderException e) { + e.printStackTrace(); + } + } - private static void readOggChaptersFromInputStream(Playable p, - InputStream input) { - if (BuildConfig.DEBUG) - Log.d(TAG, - "Trying to read chapters from item with title " - + p.getEpisodeTitle()); - try { - VorbisCommentChapterReader reader = new VorbisCommentChapterReader(); - reader.readInputStream(input); - List<Chapter> chapters = reader.getChapters(); - if (chapters != null) { - Collections.sort(chapters, new ChapterStartTimeComparator()); - processChapters(chapters, p); - if (chaptersValid(chapters)) { - p.setChapters(chapters); - Log.i(TAG, "Chapters loaded"); - } else { - Log.e(TAG, "Chapter data was invalid"); - } - } else { - Log.i(TAG, - "ChapterReader could not find any Ogg vorbis chapters"); - } - } catch (VorbisCommentReaderException e) { - e.printStackTrace(); - } - } + /** + * Makes sure that chapter does a title and an item attribute. + */ + private static void processChapters(List<Chapter> chapters, Playable p) { + for (int i = 0; i < chapters.size(); i++) { + Chapter c = chapters.get(i); + if (c.getTitle() == null) { + c.setTitle(Integer.toString(i)); + } + } + } - /** Makes sure that chapter does a title and an item attribute. */ - private static void processChapters(List<Chapter> chapters, Playable p) { - for (int i = 0; i < chapters.size(); i++) { - Chapter c = chapters.get(i); - if (c.getTitle() == null) { - c.setTitle(Integer.toString(i)); - } - } - } + private static boolean chaptersValid(List<Chapter> chapters) { + if (chapters.isEmpty()) { + return false; + } + for (Chapter c : chapters) { + if (c.getTitle() == null) { + return false; + } + if (c.getStart() < 0) { + return false; + } + } + return true; + } - private static boolean chaptersValid(List<Chapter> chapters) { - if (chapters.isEmpty()) { - return false; - } - for (Chapter c : chapters) { - if (c.getTitle() == null) { - return false; - } - if (c.getStart() < 0) { - return false; - } - } - return true; - } + /** + * Calls getCurrentChapter with current position. + */ + public static Chapter getCurrentChapter(Playable media) { + if (media.getChapters() != null) { + List<Chapter> chapters = media.getChapters(); + Chapter current = null; + if (chapters != null) { + current = chapters.get(0); + for (Chapter sc : chapters) { + if (sc.getStart() > media.getPosition()) { + break; + } else { + current = sc; + } + } + } + return current; + } else { + return null; + } + } - /** Calls getCurrentChapter with current position. */ - public static Chapter getCurrentChapter(Playable media) { - if (media.getChapters() != null) { - List<Chapter> chapters = media.getChapters(); - Chapter current = null; - if (chapters != null) { - current = chapters.get(0); - for (Chapter sc : chapters) { - if (sc.getStart() > media.getPosition()) { - break; - } else { - current = sc; - } - } - } - return current; - } else { - return null; - } - } + public static void loadChaptersFromStreamUrl(Playable media) { + if (BuildConfig.DEBUG) + Log.d(TAG, "Starting chapterLoader thread"); + ChapterUtils.readID3ChaptersFromPlayableStreamUrl(media); + if (media.getChapters() == null) { + ChapterUtils.readOggChaptersFromPlayableStreamUrl(media); + } - public static void loadChaptersFromStreamUrl(Playable media) { - if (BuildConfig.DEBUG) - Log.d(TAG, "Starting chapterLoader thread"); - ChapterUtils.readID3ChaptersFromPlayableStreamUrl(media); - if (media.getChapters() == null) { - ChapterUtils.readOggChaptersFromPlayableStreamUrl(media); - } + if (BuildConfig.DEBUG) + Log.d(TAG, "ChapterLoaderThread has finished"); + } - if (BuildConfig.DEBUG) - Log.d(TAG, "ChapterLoaderThread has finished"); - } - - public static void loadChaptersFromFileUrl(Playable media) { - if (media.localFileAvailable()) { - ChapterUtils.readID3ChaptersFromPlayableFileUrl(media); - if (media.getChapters() == null) { - ChapterUtils.readOggChaptersFromPlayableFileUrl(media); - } - } else { - Log.e(TAG, "Could not load chapters from file url: local file not available"); - } - } + public static void loadChaptersFromFileUrl(Playable media) { + if (media.localFileAvailable()) { + ChapterUtils.readID3ChaptersFromPlayableFileUrl(media); + if (media.getChapters() == null) { + ChapterUtils.readOggChaptersFromPlayableFileUrl(media); + } + } else { + Log.e(TAG, "Could not load chapters from file url: local file not available"); + } + } } diff --git a/src/de/danoeh/antennapod/util/URLChecker.java b/src/de/danoeh/antennapod/util/URLChecker.java index 9997daaf7..2352adddf 100644 --- a/src/de/danoeh/antennapod/util/URLChecker.java +++ b/src/de/danoeh/antennapod/util/URLChecker.java @@ -22,6 +22,8 @@ public final class URLChecker { */ private static final String TAG = "URLChecker"; + private static final String AP_SUBSCRIBE = "antennapod-subscribe://"; + /** * Checks if URL is valid and modifies it if necessary. * @@ -29,23 +31,24 @@ public final class URLChecker { * @return The prepared url */ public static String prepareURL(String url) { - StringBuilder builder = new StringBuilder(); url = StringUtils.trim(url); if (url.startsWith("feed://")) { if (BuildConfig.DEBUG) Log.d(TAG, "Replacing feed:// with http://"); - url = url.replaceFirst("feed://", "http://"); + return url.replaceFirst("feed://", "http://"); } else if (url.startsWith("pcast://")) { - if (BuildConfig.DEBUG) Log.d(TAG, "Replacing pcast:// with http://"); - url = url.replaceFirst("pcast://", "http://"); + if (BuildConfig.DEBUG) Log.d(TAG, "Removing pcast://"); + return prepareURL(StringUtils.removeStart(url, "pcast://")); } else if (url.startsWith("itpc")) { if (BuildConfig.DEBUG) Log.d(TAG, "Replacing itpc:// with http://"); - url = url.replaceFirst("itpc://", "http://"); + return url.replaceFirst("itpc://", "http://"); + } else if (url.startsWith(AP_SUBSCRIBE)) { + if (BuildConfig.DEBUG) Log.d(TAG, "Removing antennapod-subscribe://"); + return prepareURL(StringUtils.removeStart(url, AP_SUBSCRIBE)); } else if (!(url.startsWith("http://") || url.startsWith("https://"))) { if (BuildConfig.DEBUG) Log.d(TAG, "Adding http:// at the beginning of the URL"); - builder.append("http://"); + return "http://" + url; + } else { + return url; } - builder.append(url); - - return builder.toString(); } } diff --git a/src/de/danoeh/antennapod/util/flattr/FlattrUtils.java b/src/de/danoeh/antennapod/util/flattr/FlattrUtils.java index 1ff3437c0..96d3bbedd 100644 --- a/src/de/danoeh/antennapod/util/flattr/FlattrUtils.java +++ b/src/de/danoeh/antennapod/util/flattr/FlattrUtils.java @@ -90,7 +90,7 @@ public class FlattrUtils { */ public static boolean hasAPICredentials() { return StringUtils.isNotEmpty(BuildConfig.FLATTR_APP_KEY) - && StringUtils.isNoneEmpty(BuildConfig.FLATTR_APP_SECRET); + && StringUtils.isNotEmpty(BuildConfig.FLATTR_APP_SECRET); } public static boolean hasToken() { @@ -133,7 +133,7 @@ public class FlattrUtils { throws FlattrException { if (hasToken()) { FlattrService fs = FlattrServiceCreator.getService(retrieveToken()); - fs.click(url); + fs.flattr(url); } else { Log.e(TAG, "clickUrl was called with null access token"); } |