summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--app/src/main/java/de/danoeh/antennapod/adapter/gpodnet/PodcastListAdapter.java15
-rw-r--r--app/src/main/java/de/danoeh/antennapod/adapter/gpodnet/TagListAdapter.java54
-rw-r--r--app/src/main/java/de/danoeh/antennapod/adapter/itunes/ItunesAdapter.java187
-rw-r--r--app/src/main/java/de/danoeh/antennapod/fragment/AddFeedFragment.java10
-rw-r--r--app/src/main/java/de/danoeh/antennapod/fragment/ItunesSearchFragment.java193
-rw-r--r--app/src/main/java/de/danoeh/antennapod/fragment/QueueFragment.java37
-rw-r--r--app/src/main/java/de/danoeh/antennapod/fragment/gpodnet/TagFragment.java12
-rw-r--r--app/src/main/java/de/danoeh/antennapod/fragment/gpodnet/TagListFragment.java19
-rw-r--r--app/src/main/java/de/danoeh/antennapod/menuhandler/MenuItemUtils.java2
-rw-r--r--app/src/main/java/de/danoeh/antennapod/preferences/CustomEditTextPreference.java33
-rw-r--r--app/src/main/java/de/danoeh/antennapod/preferences/PreferenceController.java60
-rw-r--r--app/src/main/res/layout/addfeed.xml9
-rw-r--r--app/src/main/res/layout/fragment_itunes_search.xml26
-rw-r--r--app/src/main/res/layout/gpodnet_podcast_listitem.xml54
-rw-r--r--app/src/main/res/layout/gpodnet_tag_listitem.xml34
-rw-r--r--app/src/main/res/layout/itunes_podcast_listitem.xml38
-rw-r--r--app/src/main/res/layout/queue_listitem.xml5
-rw-r--r--app/src/main/res/menu/feedlist.xml2
-rw-r--r--app/src/main/res/menu/new_episodes.xml2
-rw-r--r--app/src/main/res/menu/queue.xml2
-rw-r--r--app/src/main/res/xml/preferences.xml6
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/feed/FeedItem.java4
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/gpoddernet/GpodnetService.java102
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetTag.java54
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/preferences/UserPreferences.java11
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadService.java107
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/syndication/handler/SyndHandler.java24
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/syndication/namespace/NSDublinCore.java37
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/syndication/util/SyndDateUtils.java27
-rwxr-xr-xcore/src/main/res/drawable-hdpi/ic_feed_grey600_24dp.pngbin0 -> 1601 bytes
-rwxr-xr-xcore/src/main/res/drawable-hdpi/ic_feed_white_24dp.pngbin0 -> 1367 bytes
-rwxr-xr-xcore/src/main/res/drawable-mdpi/ic_feed_grey600_24dp.pngbin0 -> 1018 bytes
-rwxr-xr-xcore/src/main/res/drawable-mdpi/ic_feed_white_24dp.pngbin0 -> 875 bytes
-rwxr-xr-xcore/src/main/res/drawable-xhdpi/ic_feed_grey600_24dp.pngbin0 -> 2223 bytes
-rwxr-xr-xcore/src/main/res/drawable-xhdpi/ic_feed_white_24dp.pngbin0 -> 1933 bytes
-rwxr-xr-xcore/src/main/res/drawable-xxhdpi/ic_feed_grey600_24dp.pngbin0 -> 3265 bytes
-rwxr-xr-xcore/src/main/res/drawable-xxhdpi/ic_feed_white_24dp.pngbin0 -> 2884 bytes
-rw-r--r--core/src/main/res/values-zh-rCN/strings.xml53
-rw-r--r--core/src/main/res/values/attrs.xml1
-rw-r--r--core/src/main/res/values/strings.xml5
-rw-r--r--core/src/main/res/values/styles.xml4
42 files changed, 1068 insertions, 162 deletions
diff --git a/.gitignore b/.gitignore
index 153ef778f..482ba1839 100644
--- a/.gitignore
+++ b/.gitignore
@@ -41,3 +41,4 @@ libs
*.DS_Store
src/de/danoeh/antennapod/util/flattr/FlattrConfig.java
gradle.properties
+*.keystore
diff --git a/app/src/main/java/de/danoeh/antennapod/adapter/gpodnet/PodcastListAdapter.java b/app/src/main/java/de/danoeh/antennapod/adapter/gpodnet/PodcastListAdapter.java
index 58af2c4d5..b85709c5e 100644
--- a/app/src/main/java/de/danoeh/antennapod/adapter/gpodnet/PodcastListAdapter.java
+++ b/app/src/main/java/de/danoeh/antennapod/adapter/gpodnet/PodcastListAdapter.java
@@ -39,16 +39,15 @@ public class PodcastListAdapter extends ArrayAdapter<GpodnetPodcast> {
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
convertView = inflater.inflate(R.layout.gpodnet_podcast_listitem, parent, false);
- holder.title = (TextView) convertView.findViewById(R.id.txtvTitle);
holder.image = (ImageView) convertView.findViewById(R.id.imgvCover);
-
+ holder.title = (TextView) convertView.findViewById(R.id.txtvTitle);
+ holder.subscribers = (TextView) convertView.findViewById(R.id.txtvSubscribers);
+ holder.url = (TextView) convertView.findViewById(R.id.txtvUrl);
convertView.setTag(holder);
} else {
holder = (Holder) convertView.getTag();
}
- holder.title.setText(podcast.getTitle());
-
if (StringUtils.isNotBlank(podcast.getLogoUrl())) {
Picasso.with(convertView.getContext())
.load(podcast.getLogoUrl())
@@ -56,11 +55,17 @@ public class PodcastListAdapter extends ArrayAdapter<GpodnetPodcast> {
.into(holder.image);
}
+ holder.title.setText(podcast.getTitle());
+ holder.subscribers.setText(String.valueOf(podcast.getSubscribers()));
+ holder.url.setText(podcast.getUrl());
+
return convertView;
}
static class Holder {
- TextView title;
ImageView image;
+ TextView title;
+ TextView subscribers;
+ TextView url;
}
}
diff --git a/app/src/main/java/de/danoeh/antennapod/adapter/gpodnet/TagListAdapter.java b/app/src/main/java/de/danoeh/antennapod/adapter/gpodnet/TagListAdapter.java
new file mode 100644
index 000000000..b4eadefb5
--- /dev/null
+++ b/app/src/main/java/de/danoeh/antennapod/adapter/gpodnet/TagListAdapter.java
@@ -0,0 +1,54 @@
+package de.danoeh.antennapod.adapter.gpodnet;
+
+import android.content.Context;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import android.widget.TextView;
+
+import java.util.List;
+
+import de.danoeh.antennapod.R;
+import de.danoeh.antennapod.core.gpoddernet.model.GpodnetTag;
+
+/**
+ * Adapter for displaying a list of GPodnetPodcast-Objects.
+ */
+public class TagListAdapter extends ArrayAdapter<GpodnetTag> {
+
+ public TagListAdapter(Context context, int resource, List<GpodnetTag> objects) {
+ super(context, resource, objects);
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ Holder holder;
+
+ GpodnetTag tag = getItem(position);
+
+ // Inflate Layout
+ if (convertView == null) {
+ holder = new Holder();
+ LayoutInflater inflater = (LayoutInflater) getContext()
+ .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+
+ convertView = inflater.inflate(R.layout.gpodnet_tag_listitem, parent, false);
+ holder.title = (TextView) convertView.findViewById(R.id.txtvTitle);
+ holder.usage = (TextView) convertView.findViewById(R.id.txtvUsage);
+ convertView.setTag(holder);
+ } else {
+ holder = (Holder) convertView.getTag();
+ }
+
+ holder.title.setText(tag.getTitle());
+ holder.usage.setText(String.valueOf(tag.getUsage()));
+
+ return convertView;
+ }
+
+ static class Holder {
+ TextView title;
+ TextView usage;
+ }
+}
diff --git a/app/src/main/java/de/danoeh/antennapod/adapter/itunes/ItunesAdapter.java b/app/src/main/java/de/danoeh/antennapod/adapter/itunes/ItunesAdapter.java
new file mode 100644
index 000000000..4fc2838b7
--- /dev/null
+++ b/app/src/main/java/de/danoeh/antennapod/adapter/itunes/ItunesAdapter.java
@@ -0,0 +1,187 @@
+package de.danoeh.antennapod.adapter.itunes;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.os.AsyncTask;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import org.apache.http.HttpResponse;
+import org.apache.http.client.HttpClient;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.impl.client.DefaultHttpClient;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.IOException;
+import java.util.List;
+
+import de.danoeh.antennapod.R;
+import de.danoeh.antennapod.activity.MainActivity;
+
+public class ItunesAdapter extends ArrayAdapter<ItunesAdapter.Podcast> {
+ /**
+ * Related Context
+ */
+ private final Context context;
+
+ /**
+ * List holding the podcasts found in the search
+ */
+ private final List<Podcast> data;
+
+ /**
+ * Constructor.
+ *
+ * @param context Related context
+ * @param objects Search result
+ */
+ public ItunesAdapter(Context context, List<Podcast> objects) {
+ super(context, 0, objects);
+ this.data = objects;
+ this.context = context;
+ }
+
+ /**
+ * Updates the given ImageView with the image in the given Podcast's imageUrl
+ */
+ class FetchImageTask extends AsyncTask<Void,Void,Bitmap>{
+ /**
+ * Current podcast
+ */
+ private final Podcast podcast;
+
+ /**
+ * ImageView to be updated
+ */
+ private final ImageView imageView;
+
+ /**
+ * Constructor
+ *
+ * @param podcast Podcast that has the image
+ * @param imageView UI image to be updated
+ */
+ FetchImageTask(Podcast podcast, ImageView imageView){
+ this.podcast = podcast;
+ this.imageView = imageView;
+ }
+
+ //Get the image from the url
+ @Override
+ protected Bitmap doInBackground(Void... params) {
+ HttpClient client = new DefaultHttpClient();
+ HttpGet get = new HttpGet(podcast.imageUrl);
+ try {
+ HttpResponse response = client.execute(get);
+ return BitmapFactory.decodeStream(response.getEntity().getContent());
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ return null;
+ }
+
+ //Set the background image for the podcast
+ @Override
+ protected void onPostExecute(Bitmap img) {
+ super.onPostExecute(img);
+ if(img!=null) {
+ imageView.setImageBitmap(img);
+ }
+ }
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ //Current podcast
+ Podcast podcast = data.get(position);
+
+ //ViewHolder
+ PodcastViewHolder viewHolder;
+
+ //Resulting view
+ View view;
+
+ //Handle view holder stuff
+ if(convertView == null) {
+ view = ((MainActivity) context).getLayoutInflater()
+ .inflate(R.layout.itunes_podcast_listitem, parent, false);
+ viewHolder = new PodcastViewHolder(view);
+ view.setTag(viewHolder);
+ } else {
+ view = convertView;
+ viewHolder = (PodcastViewHolder) view.getTag();
+ }
+
+ //Set the title
+ viewHolder.titleView.setText(podcast.title);
+
+ //Update the empty imageView with the image from the feed
+ new FetchImageTask(podcast,viewHolder.coverView).execute();
+
+ //Feed the grid view
+ return view;
+ }
+
+ /**
+ * View holder object for the GridView
+ */
+ class PodcastViewHolder {
+
+ /**
+ * ImageView holding the Podcast image
+ */
+ public final ImageView coverView;
+
+ /**
+ * TextView holding the Podcast title
+ */
+ public final TextView titleView;
+
+
+ /**
+ * Constructor
+ * @param view GridView cell
+ */
+ PodcastViewHolder(View view){
+ coverView = (ImageView) view.findViewById(R.id.imgvCover);
+ titleView = (TextView) view.findViewById(R.id.txtvTitle);
+ }
+ }
+
+ /**
+ * Represents an individual podcast on the iTunes Store.
+ */
+ public static class Podcast { //TODO: Move this out eventually. Possibly to core.itunes.model
+
+ /**
+ * The name of the podcast
+ */
+ public final String title;
+
+ /**
+ * URL of the podcast image
+ */
+ public final String imageUrl;
+ /**
+ * URL of the podcast feed
+ */
+ public final String feedUrl;
+
+ /**
+ * Constructor.
+ *
+ * @param json object holding the podcast information
+ * @throws JSONException
+ */
+ public Podcast(JSONObject json) throws JSONException {
+ title = json.getString("collectionName");
+ imageUrl = json.getString("artworkUrl100");
+ feedUrl = json.getString("feedUrl");
+ }
+ }
+}
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 f5ae5a777..e4ae1683b 100644
--- a/app/src/main/java/de/danoeh/antennapod/fragment/AddFeedFragment.java
+++ b/app/src/main/java/de/danoeh/antennapod/fragment/AddFeedFragment.java
@@ -8,6 +8,7 @@ import android.view.View;
import android.view.ViewGroup;
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;
@@ -41,10 +42,18 @@ public class AddFeedFragment extends Fragment {
Button butBrowserGpoddernet = (Button) root.findViewById(R.id.butBrowseGpoddernet);
Button butOpmlImport = (Button) root.findViewById(R.id.butOpmlImport);
Button butConfirm = (Button) root.findViewById(R.id.butConfirm);
+ Button butSearchITunes = (Button) root.findViewById(R.id.butSearchItunes);
final MainActivity activity = (MainActivity) getActivity();
activity.getMainActivtyActionBar().setTitle(R.string.add_feed_label);
+ butSearchITunes.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ activity.loadChildFragment(new ItunesSearchFragment());
+ }
+ });
+
butBrowserGpoddernet.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
@@ -53,7 +62,6 @@ public class AddFeedFragment extends Fragment {
});
butOpmlImport.setOnClickListener(new View.OnClickListener() {
-
@Override
public void onClick(View v) {
startActivity(new Intent(getActivity(),
diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/ItunesSearchFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/ItunesSearchFragment.java
new file mode 100644
index 000000000..c14b0cc6e
--- /dev/null
+++ b/app/src/main/java/de/danoeh/antennapod/fragment/ItunesSearchFragment.java
@@ -0,0 +1,193 @@
+package de.danoeh.antennapod.fragment;
+
+import android.content.Intent;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.support.v4.app.Fragment;
+import android.support.v7.widget.SearchView;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.GridView;
+
+import org.apache.http.HttpResponse;
+import org.apache.http.client.HttpClient;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.impl.client.DefaultHttpClient;
+import org.apache.http.util.EntityUtils;
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.IOException;
+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 static de.danoeh.antennapod.adapter.itunes.ItunesAdapter.*;
+
+//Searches iTunes store for given string and displays results in a list
+public class ItunesSearchFragment extends Fragment {
+ final String TAG = "ItunesSearchFragment";
+ /**
+ * Search input field
+ */
+ private SearchView searchView;
+
+ /**
+ * Adapter responsible with the search results
+ */
+ private ItunesAdapter adapter;
+
+ /**
+ * List of podcasts retreived from the search
+ */
+ private List<Podcast> searchResults;
+
+ /**
+ * Replace adapter data with provided search results from SearchTask.
+ * @param result List of Podcast objects containing search results
+ */
+ void updateData(List<Podcast> result) {
+ this.searchResults = result;
+ adapter.clear();
+
+ //ArrayAdapter.addAll() requires minsdk > 10
+ for(Podcast p: result) {
+ adapter.add(p);
+ }
+
+ adapter.notifyDataSetInvalidated();
+ }
+
+ /**
+ * Constructor
+ */
+ public ItunesSearchFragment() {
+ // Required empty public constructor
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ adapter = new ItunesAdapter(getActivity(), new ArrayList<Podcast>());
+
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ // Inflate the layout for this fragment
+ View view = inflater.inflate(R.layout.fragment_itunes_search, container, false);
+ GridView gridView = (GridView) view.findViewById(R.id.gridView);
+ gridView.setAdapter(adapter);
+
+ //Show information about the podcast when the list item is clicked
+ gridView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
+ @Override
+ public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+ Intent intent = new Intent(getActivity(),
+ DefaultOnlineFeedViewActivity.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");
+ startActivity(intent);
+ }
+ });
+
+ //Configure search input view to be expanded by default with a visible submit button
+ searchView = (SearchView) view.findViewById(R.id.itunes_search_view);
+ searchView.setIconifiedByDefault(false);
+ searchView.setIconified(false);
+ searchView.setSubmitButtonEnabled(true);
+ searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
+ @Override
+ public boolean onQueryTextSubmit(String s) {
+ //This prevents onQueryTextSubmit() from being called twice when keyboard is used
+ //to submit the query.
+ searchView.clearFocus();
+ new SearchTask(s).execute();
+ return false;
+ }
+
+ @Override
+ public boolean onQueryTextChange(String s) {
+ return false;
+ }
+ });
+
+ return view;
+ }
+
+ /**
+ * Search the iTunes store for podcasts using the given query
+ */
+ class SearchTask extends AsyncTask<Void,Void,Void> {
+ /**
+ * Incomplete iTunes API search URL
+ */
+ final String apiUrl = "https://itunes.apple.com/search?media=podcast&term=%s";
+
+ /**
+ * Search terms
+ */
+ final String query;
+
+ /**
+ * Search result
+ */
+ final List<Podcast> taskData = new ArrayList<>();
+
+ /**
+ * Constructor
+ *
+ * @param query Search string
+ */
+ public SearchTask(String query){
+ this.query = query;
+ }
+
+ //Get the podcast data
+ @Override
+ protected Void doInBackground(Void... params) {
+
+ //Spaces in the query need to be replaced with '+' character.
+ String formattedUrl = String.format(apiUrl, query).replace(' ', '+');
+
+ HttpClient client = new DefaultHttpClient();
+ HttpGet get = new HttpGet(formattedUrl);
+
+ try {
+ HttpResponse response = client.execute(get);
+ String resultString = EntityUtils.toString(response.getEntity());
+ JSONObject result = new JSONObject(resultString);
+ JSONArray j = result.getJSONArray("results");
+
+ for (int i = 0; i < j.length(); i++){
+ JSONObject podcastJson = j.getJSONObject(i);
+ Podcast podcast = new Podcast(podcastJson);
+ taskData.add(podcast);
+ }
+
+ } catch (IOException | JSONException e) {
+ e.printStackTrace();
+ }
+ return null;
+ }
+
+ //Save the data and update the list
+ @Override
+ protected void onPostExecute(Void aVoid) {
+ super.onPostExecute(aVoid);
+ updateData(taskData);
+ }
+ }
+}
diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/QueueFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/QueueFragment.java
index 3e60f1af0..da33c6ea3 100644
--- a/app/src/main/java/de/danoeh/antennapod/fragment/QueueFragment.java
+++ b/app/src/main/java/de/danoeh/antennapod/fragment/QueueFragment.java
@@ -116,13 +116,7 @@ public class QueueFragment extends Fragment {
@Override
public void onPause() {
super.onPause();
- SharedPreferences prefs = getActivity().getSharedPreferences(PREFS, Context.MODE_PRIVATE);
- SharedPreferences.Editor editor = prefs.edit();
- View v = listView.getChildAt(0);
- int top = (v == null) ? 0 : (v.getTop() - listView.getPaddingTop());
- editor.putInt(PREF_KEY_LIST_SELECTION, listView.getFirstVisiblePosition());
- editor.putInt(PREF_KEY_LIST_TOP, top);
- editor.commit();
+ saveScrollPosition();
}
@Override
@@ -138,6 +132,30 @@ public class QueueFragment extends Fragment {
this.activity.set((MainActivity) activity);
}
+ private void saveScrollPosition() {
+ SharedPreferences prefs = getActivity().getSharedPreferences(PREFS, Context.MODE_PRIVATE);
+ SharedPreferences.Editor editor = prefs.edit();
+ View v = listView.getChildAt(0);
+ int top = (v == null) ? 0 : (v.getTop() - listView.getPaddingTop());
+ editor.putInt(PREF_KEY_LIST_SELECTION, listView.getFirstVisiblePosition());
+ editor.putInt(PREF_KEY_LIST_TOP, top);
+ editor.commit();
+ }
+
+ private void restoreScrollPosition() {
+ SharedPreferences prefs = getActivity().getSharedPreferences(PREFS, Context.MODE_PRIVATE);
+ int listSelection = prefs.getInt(PREF_KEY_LIST_SELECTION, 0);
+ int top = prefs.getInt(PREF_KEY_LIST_TOP, 0);
+ if(listSelection > 0 || top > 0) {
+ listView.setSelectionFromTop(listSelection, top);
+ // restore once, then forget
+ SharedPreferences.Editor editor = prefs.edit();
+ editor.putInt(PREF_KEY_LIST_SELECTION, 0);
+ editor.putInt(PREF_KEY_LIST_TOP, 0);
+ editor.commit();
+ }
+ }
+
private void resetViewState() {
unregisterForContextMenu(listView);
listAdapter = null;
@@ -374,10 +392,7 @@ public class QueueFragment extends Fragment {
}
listAdapter.notifyDataSetChanged();
- SharedPreferences prefs = getActivity().getSharedPreferences(PREFS, Context.MODE_PRIVATE);
- int listSelection = prefs.getInt(PREF_KEY_LIST_SELECTION, 0);
- int top = prefs.getInt(PREF_KEY_LIST_TOP, 0);
- listView.setSelectionFromTop(listSelection, top);
+ restoreScrollPosition();
// we need to refresh the options menu because it sometimes
// needs data that may have just been loaded.
diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/gpodnet/TagFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/gpodnet/TagFragment.java
index c8cdbcfed..e2450f03d 100644
--- a/app/src/main/java/de/danoeh/antennapod/fragment/gpodnet/TagFragment.java
+++ b/app/src/main/java/de/danoeh/antennapod/fragment/gpodnet/TagFragment.java
@@ -24,11 +24,11 @@ public class TagFragment extends PodcastListFragment {
private GpodnetTag tag;
- public static TagFragment newInstance(String tagName) {
- Validate.notNull(tagName);
+ public static TagFragment newInstance(GpodnetTag tag) {
+ Validate.notNull(tag);
TagFragment fragment = new TagFragment();
Bundle args = new Bundle();
- args.putString("tag", tagName);
+ args.putParcelable("tag", tag);
fragment.setArguments(args);
return fragment;
}
@@ -38,14 +38,14 @@ public class TagFragment extends PodcastListFragment {
super.onCreate(savedInstanceState);
Bundle args = getArguments();
- Validate.isTrue(args != null && args.getString("tag") != null, "args invalid");
- tag = new GpodnetTag(args.getString("tag"));
+ Validate.isTrue(args != null && args.getParcelable("tag") != null, "args invalid");
+ tag = args.getParcelable("tag");
}
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
- ((MainActivity) getActivity()).getMainActivtyActionBar().setTitle(tag.getName());
+ ((MainActivity) getActivity()).getMainActivtyActionBar().setTitle(tag.getTitle());
}
@Override
diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/gpodnet/TagListFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/gpodnet/TagListFragment.java
index 24e0e4caa..cc87407b4 100644
--- a/app/src/main/java/de/danoeh/antennapod/fragment/gpodnet/TagListFragment.java
+++ b/app/src/main/java/de/danoeh/antennapod/fragment/gpodnet/TagListFragment.java
@@ -10,14 +10,13 @@ import android.view.Menu;
import android.view.MenuInflater;
import android.view.View;
import android.widget.AdapterView;
-import android.widget.ArrayAdapter;
import android.widget.TextView;
-import java.util.ArrayList;
import java.util.List;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.activity.MainActivity;
+import de.danoeh.antennapod.adapter.gpodnet.TagListAdapter;
import de.danoeh.antennapod.core.gpoddernet.GpodnetService;
import de.danoeh.antennapod.core.gpoddernet.GpodnetServiceException;
import de.danoeh.antennapod.core.gpoddernet.model.GpodnetTag;
@@ -67,9 +66,9 @@ public class TagListFragment extends ListFragment {
getListView().setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
- String selectedTag = (String) getListAdapter().getItem(position);
+ GpodnetTag tag = (GpodnetTag) getListAdapter().getItem(position);
MainActivity activity = (MainActivity) getActivity();
- activity.loadChildFragment(TagFragment.newInstance(selectedTag));
+ activity.loadChildFragment(TagFragment.newInstance(tag));
}
});
@@ -77,6 +76,12 @@ public class TagListFragment extends ListFragment {
}
@Override
+ public void onResume() {
+ super.onResume();
+ ((MainActivity) getActivity()).getMainActivtyActionBar().setTitle(R.string.add_feed_label);
+ }
+
+ @Override
public void onDestroyView() {
super.onDestroyView();
cancelLoadTask();
@@ -121,11 +126,7 @@ public class TagListFragment extends ListFragment {
final Context context = getActivity();
if (context != null) {
if (gpodnetTags != null) {
- List<String> tagNames = new ArrayList<String>();
- for (GpodnetTag tag : gpodnetTags) {
- tagNames.add(tag.getName());
- }
- setListAdapter(new ArrayAdapter<String>(context, android.R.layout.simple_list_item_1, tagNames));
+ setListAdapter(new TagListAdapter(context, android.R.layout.simple_list_item_1, gpodnetTags));
} else if (exception != null) {
TextView txtvError = new TextView(getActivity());
txtvError.setText(exception.getMessage());
diff --git a/app/src/main/java/de/danoeh/antennapod/menuhandler/MenuItemUtils.java b/app/src/main/java/de/danoeh/antennapod/menuhandler/MenuItemUtils.java
index 05d6ded4d..fc942ce20 100644
--- a/app/src/main/java/de/danoeh/antennapod/menuhandler/MenuItemUtils.java
+++ b/app/src/main/java/de/danoeh/antennapod/menuhandler/MenuItemUtils.java
@@ -14,7 +14,7 @@ public class MenuItemUtils extends de.danoeh.antennapod.core.menuhandler.MenuIte
public static MenuItem addSearchItem(Menu menu, SearchView searchView) {
MenuItem item = menu.add(Menu.NONE, R.id.search_item, Menu.NONE, R.string.search_label);
- MenuItemCompat.setShowAsAction(item, MenuItemCompat.SHOW_AS_ACTION_IF_ROOM);
+ MenuItemCompat.setShowAsAction(item, MenuItemCompat.SHOW_AS_ACTION_ALWAYS);
MenuItemCompat.setActionView(item, searchView);
return item;
}
diff --git a/app/src/main/java/de/danoeh/antennapod/preferences/CustomEditTextPreference.java b/app/src/main/java/de/danoeh/antennapod/preferences/CustomEditTextPreference.java
new file mode 100644
index 000000000..898a56004
--- /dev/null
+++ b/app/src/main/java/de/danoeh/antennapod/preferences/CustomEditTextPreference.java
@@ -0,0 +1,33 @@
+package de.danoeh.antennapod.preferences;
+
+import android.app.AlertDialog;
+import android.content.Context;
+import android.os.Build;
+import android.preference.EditTextPreference;
+import android.util.AttributeSet;
+
+import de.danoeh.antennapod.R;
+
+public class CustomEditTextPreference extends EditTextPreference {
+
+ public CustomEditTextPreference(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ }
+
+ public CustomEditTextPreference(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public CustomEditTextPreference(Context context) {
+ super(context);
+ }
+
+ @Override
+ protected void onPrepareDialogBuilder(AlertDialog.Builder builder) {
+ if(Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
+ builder.setInverseBackgroundForced(true);
+ getEditText().setTextColor(getContext().getResources().getColor(R.color.black));
+ }
+ }
+
+}
diff --git a/app/src/main/java/de/danoeh/antennapod/preferences/PreferenceController.java b/app/src/main/java/de/danoeh/antennapod/preferences/PreferenceController.java
index 43f942308..227ea8dfb 100644
--- a/app/src/main/java/de/danoeh/antennapod/preferences/PreferenceController.java
+++ b/app/src/main/java/de/danoeh/antennapod/preferences/PreferenceController.java
@@ -9,10 +9,14 @@ import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiManager;
import android.os.Build;
import android.preference.CheckBoxPreference;
+import android.preference.EditTextPreference;
import android.preference.ListPreference;
import android.preference.Preference;
import android.preference.PreferenceScreen;
+import android.text.Editable;
+import android.text.TextWatcher;
import android.util.Log;
+import android.widget.EditText;
import android.widget.Toast;
import java.io.File;
@@ -214,6 +218,52 @@ public class PreferenceController {
}
}
);
+ ui.findPreference(UserPreferences.PREF_PARALLEL_DOWNLOADS)
+ .setOnPreferenceChangeListener(
+ new Preference.OnPreferenceChangeListener() {
+ @Override
+ public boolean onPreferenceChange(Preference preference, Object o) {
+ if (o instanceof String) {
+ try {
+ int value = Integer.valueOf((String) o);
+ if (1 <= value && value <= 50) {
+ setParallelDownloadsText(value);
+ return true;
+ }
+ } catch(NumberFormatException e) {
+ return false;
+ }
+ }
+ return false;
+ }
+ }
+ );
+ // validate and set correct value: number of downloads between 1 and 50 (inclusive)
+ final EditText ev = ((EditTextPreference)ui.findPreference(UserPreferences.PREF_PARALLEL_DOWNLOADS)).getEditText();
+ ev.addTextChangedListener(new TextWatcher() {
+ @Override
+ public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
+
+ @Override
+ public void onTextChanged(CharSequence s, int start, int before, int count) {}
+
+ @Override
+ public void afterTextChanged(Editable s) {
+ if(s.length() > 0) {
+ try {
+ int value = Integer.valueOf(s.toString());
+ if (value <= 0) {
+ ev.setText("1");
+ } else if (value > 50) {
+ ev.setText("50");
+ }
+ } catch(NumberFormatException e) {
+ ev.setText("6");
+ }
+ ev.setSelection(ev.getText().length());
+ }
+ }
+ });
ui.findPreference(UserPreferences.PREF_EPISODE_CACHE_SIZE)
.setOnPreferenceChangeListener(
new Preference.OnPreferenceChangeListener() {
@@ -300,6 +350,7 @@ public class PreferenceController {
public void onResume() {
checkItemVisibility();
+ setParallelDownloadsText(UserPreferences.getParallelDownloads());
setEpisodeCacheSizeText(UserPreferences.getEpisodeCacheSize());
setDataFolderText();
updateGpodnetPreferenceScreen();
@@ -379,6 +430,15 @@ public class PreferenceController {
.setEnabled(UserPreferences.isEnableAutodownload());
}
+
+ private void setParallelDownloadsText(int downloads) {
+ final Resources res = ui.getActivity().getResources();
+
+ String s = Integer.toString(downloads)
+ + res.getString(R.string.parallel_downloads_suffix);
+ ui.findPreference(UserPreferences.PREF_PARALLEL_DOWNLOADS).setSummary(s);
+ }
+
private void setEpisodeCacheSizeText(int cacheSize) {
final Resources res = ui.getActivity().getResources();
diff --git a/app/src/main/res/layout/addfeed.xml b/app/src/main/res/layout/addfeed.xml
index 09502eb7b..a740d88cf 100644
--- a/app/src/main/res/layout/addfeed.xml
+++ b/app/src/main/res/layout/addfeed.xml
@@ -66,12 +66,19 @@
android:layout_margin="8dp"
android:text="@string/browse_gpoddernet_label"/>
+ <Button
+ android:id="@+id/butSearchItunes"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_below="@id/butBrowseGpoddernet"
+ android:layout_margin="8dp"
+ android:text="@string/search_itunes_label"/>
<TextView
android:id="@+id/txtvOpmlImport"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_below="@id/butBrowseGpoddernet"
+ android:layout_below="@id/butSearchItunes"
android:layout_margin="8dp"
style="@style/AntennaPod.TextView.Heading"
android:text="@string/opml_import_label"/>
diff --git a/app/src/main/res/layout/fragment_itunes_search.xml b/app/src/main/res/layout/fragment_itunes_search.xml
new file mode 100644
index 000000000..17ffe349b
--- /dev/null
+++ b/app/src/main/res/layout/fragment_itunes_search.xml
@@ -0,0 +1,26 @@
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+xmlns:tools="http://schemas.android.com/tools"
+android:layout_width="match_parent"
+android:layout_height="match_parent"
+tools:context="de.danoeh.antennapod.activity.ITunesSearchActivity">
+<android.support.v7.widget.SearchView
+ android:id="@+id/itunes_search_view"
+ android:layout_height="wrap_content"
+ android:layout_width="match_parent"
+ />
+<GridView
+ android:id="@+id/gridView"
+ android:layout_below="@id/itunes_search_view"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:clipToPadding="false"
+ android:columnWidth="200dp"
+ android:gravity="center"
+ android:horizontalSpacing="8dp"
+ android:numColumns="auto_fit"
+ android:paddingBottom="@dimen/list_vertical_padding"
+ android:paddingTop="@dimen/list_vertical_padding"
+ android:stretchMode="columnWidth"
+ android:verticalSpacing="8dp"
+ tools:listitem="@layout/gpodnet_podcast_listitem" />
+</RelativeLayout>
diff --git a/app/src/main/res/layout/gpodnet_podcast_listitem.xml b/app/src/main/res/layout/gpodnet_podcast_listitem.xml
index 2ade8e478..84c6c280e 100644
--- a/app/src/main/res/layout/gpodnet_podcast_listitem.xml
+++ b/app/src/main/res/layout/gpodnet_podcast_listitem.xml
@@ -23,16 +23,60 @@
tools:src="@drawable/ic_stat_antenna_default"
tools:background="@android:color/holo_green_dark" />
+ <LinearLayout
+ android:id="@+id/subscribers_container"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignTop="@id/txtvTitle"
+ android:layout_alignParentRight="true"
+ android:layout_marginRight="@dimen/listitem_threeline_horizontalpadding"
+ android:orientation="horizontal">
+
+ <ImageView
+ android:id="@+id/imgFeed"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:layout_marginRight="-4dp"
+ android:src="?attr/feed" />
+
+ <TextView
+ android:id="@+id/txtvSubscribers"
+ style="@style/AntennaPod.TextView.ListItemSecondaryTitle"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:lines="1"
+ tools:text="150"
+ tools:background="@android:color/holo_green_dark" />
+
+ </LinearLayout>
+
<TextView
android:id="@+id/txtvTitle"
style="@style/AntennaPod.TextView.ListItemPrimaryTitle"
- android:layout_width="match_parent"
+ android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_centerVertical="true"
- android:layout_marginBottom="@dimen/listitem_threeline_verticalpadding"
+ android:layout_marginBottom="@dimen/list_vertical_padding"
android:layout_marginRight="@dimen/listitem_threeline_horizontalpadding"
android:layout_toRightOf="@id/imgvCover"
- android:maxLines="1"
- tools:text="Podcast title"
+ android:layout_toLeftOf="@id/subscribers_container"
+ android:layout_alignTop="@id/imgvCover"
+ android:lines="1"
+ tools:text="Title"
tools:background="@android:color/holo_green_dark" />
+
+ <TextView
+ android:id="@+id/txtvUrl"
+ style="android:style/TextAppearance.Small"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginRight="@dimen/listitem_threeline_horizontalpadding"
+ android:layout_toRightOf="@id/imgvCover"
+ android:layout_below="@id/txtvTitle"
+ android:textSize="14sp"
+ android:textColor="?android:attr/textColorSecondary"
+ android:ellipsize="middle"
+ android:maxLines="2"
+ tools:text="http://www.example.com/feed"
+ tools:background="@android:color/holo_green_dark"/>
+
</RelativeLayout>
diff --git a/app/src/main/res/layout/gpodnet_tag_listitem.xml b/app/src/main/res/layout/gpodnet_tag_listitem.xml
new file mode 100644
index 000000000..9e545e59d
--- /dev/null
+++ b/app/src/main/res/layout/gpodnet_tag_listitem.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="center_vertical"
+ tools:background="@android:color/darker_gray">
+
+ <TextView
+ android:id="@+id/txtvTitle"
+ style="@style/AntennaPod.TextView.ListItemPrimaryTitle"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginLeft="@dimen/listitem_threeline_horizontalpadding"
+ android:layout_marginTop="@dimen/listitem_threeline_verticalpadding"
+ android:layout_marginBottom="@dimen/listitem_threeline_verticalpadding"
+ android:lines="1"
+ tools:text="Tag Title"
+ tools:background="@android:color/holo_green_dark" />
+
+ <TextView
+ android:id="@+id/txtvUsage"
+ style="@style/AntennaPod.TextView.ListItemSecondaryTitle"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentRight="true"
+ android:layout_marginRight="@dimen/listitem_threeline_horizontalpadding"
+ android:layout_marginTop="@dimen/listitem_threeline_verticalpadding"
+ android:layout_marginBottom="@dimen/listitem_threeline_verticalpadding"
+ tools:text="301"
+ tools:background="@android:color/holo_green_dark"/>
+
+</RelativeLayout>
diff --git a/app/src/main/res/layout/itunes_podcast_listitem.xml b/app/src/main/res/layout/itunes_podcast_listitem.xml
new file mode 100644
index 000000000..41b1f495f
--- /dev/null
+++ b/app/src/main/res/layout/itunes_podcast_listitem.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+xmlns:tools="http://schemas.android.com/tools"
+android:layout_width="match_parent"
+android:layout_height="@dimen/listitem_threeline_height"
+tools:background="@android:color/darker_gray">
+
+<ImageView
+ android:id="@+id/imgvCover"
+ android:layout_width="@dimen/thumbnail_length_itemlist"
+ android:layout_height="@dimen/thumbnail_length_itemlist"
+ android:layout_alignParentLeft="true"
+ android:layout_centerVertical="true"
+ android:layout_marginBottom="@dimen/listitem_threeline_verticalpadding"
+ android:layout_marginLeft="@dimen/listitem_threeline_horizontalpadding"
+ android:layout_marginRight="8dp"
+ android:layout_marginTop="@dimen/listitem_threeline_verticalpadding"
+ android:adjustViewBounds="true"
+ android:contentDescription="@string/cover_label"
+ android:cropToPadding="true"
+ android:scaleType="fitXY"
+ tools:src="@drawable/ic_stat_antenna_default"
+ tools:background="@android:color/holo_green_dark" />
+
+<TextView
+ android:id="@+id/txtvTitle"
+ style="@style/AntennaPod.TextView.ListItemPrimaryTitle"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_centerVertical="true"
+ android:layout_marginBottom="@dimen/listitem_threeline_verticalpadding"
+ android:layout_marginRight="@dimen/listitem_threeline_horizontalpadding"
+ android:layout_toRightOf="@id/imgvCover"
+ android:maxLines="1"
+ tools:text="Podcast title"
+ tools:background="@android:color/holo_green_dark" />
+</RelativeLayout>
diff --git a/app/src/main/res/layout/queue_listitem.xml b/app/src/main/res/layout/queue_listitem.xml
index 5d41c52cd..d2d51378b 100644
--- a/app/src/main/res/layout/queue_listitem.xml
+++ b/app/src/main/res/layout/queue_listitem.xml
@@ -9,9 +9,10 @@
<ImageView
android:id="@+id/drag_handle"
- android:layout_width="24dp"
+ android:layout_width="104dp"
android:layout_height="match_parent"
- android:layout_margin="8dp"
+ android:layout_marginLeft="-32dp"
+ android:layout_marginRight="-32dp"
android:contentDescription="@string/drag_handle_content_description"
android:scaleType="center"
android:src="?attr/dragview_background"
diff --git a/app/src/main/res/menu/feedlist.xml b/app/src/main/res/menu/feedlist.xml
index 8d2d9e367..b6512e828 100644
--- a/app/src/main/res/menu/feedlist.xml
+++ b/app/src/main/res/menu/feedlist.xml
@@ -7,7 +7,7 @@
android:icon="?attr/navigation_refresh"
android:menuCategory="container"
android:title="@string/refresh_label"
- custom:showAsAction="ifRoom">
+ custom:showAsAction="always">
</item>
<item
android:id="@+id/refresh_complete_item"
diff --git a/app/src/main/res/menu/new_episodes.xml b/app/src/main/res/menu/new_episodes.xml
index d74e70b3b..72661a17e 100644
--- a/app/src/main/res/menu/new_episodes.xml
+++ b/app/src/main/res/menu/new_episodes.xml
@@ -7,7 +7,7 @@
android:id="@+id/refresh_item"
android:title="@string/refresh_label"
android:menuCategory="container"
- custom:showAsAction="ifRoom"
+ custom:showAsAction="always"
android:icon="?attr/navigation_refresh"/>
<item
diff --git a/app/src/main/res/menu/queue.xml b/app/src/main/res/menu/queue.xml
index 51e47c061..c7dd4d371 100644
--- a/app/src/main/res/menu/queue.xml
+++ b/app/src/main/res/menu/queue.xml
@@ -7,7 +7,7 @@
android:id="@+id/refresh_item"
android:title="@string/refresh_label"
android:menuCategory="container"
- custom:showAsAction="ifRoom"
+ custom:showAsAction="always"
android:icon="?attr/navigation_refresh"/>
<item
diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml
index cf1be1a74..6d14349d5 100644
--- a/app/src/main/res/xml/preferences.xml
+++ b/app/src/main/res/xml/preferences.xml
@@ -88,13 +88,17 @@
android:key="prefAutoUpdateIntervall"
android:summary="@string/pref_autoUpdateIntervall_sum"
android:title="@string/pref_autoUpdateIntervall_title"/>
-
<CheckBoxPreference
android:defaultValue="false"
android:enabled="true"
android:key="prefMobileUpdate"
android:summary="@string/pref_mobileUpdate_sum"
android:title="@string/pref_mobileUpdate_title"/>
+ <de.danoeh.antennapod.preferences.CustomEditTextPreference
+ android:defaultValue="6"
+ android:inputType="number"
+ android:key="prefParallelDownloads"
+ android:title="@string/pref_parallel_downloads_title"/>
<ListPreference
android:defaultValue="20"
android:entries="@array/episode_cache_size_entries"
diff --git a/core/src/main/java/de/danoeh/antennapod/core/feed/FeedItem.java b/core/src/main/java/de/danoeh/antennapod/core/feed/FeedItem.java
index c63b61f55..5a4d869e7 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/feed/FeedItem.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/feed/FeedItem.java
@@ -135,8 +135,8 @@ public class FeedItem extends FeedComponent implements ShownotesProvider, Flattr
if (other.media != null) {
if (media == null) {
setMedia(other.media);
- } else if (media.compareWithOther(other)) {
- media.updateFromOther(other);
+ } else if (media.compareWithOther(other.media)) {
+ media.updateFromOther(other.media);
}
}
if (other.paymentLink != null) {
diff --git a/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/GpodnetService.java b/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/GpodnetService.java
index 5ee40186f..a353c984a 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/GpodnetService.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/GpodnetService.java
@@ -1,5 +1,8 @@
package de.danoeh.antennapod.core.gpoddernet;
+import android.os.Build;
+import android.util.Log;
+
import com.squareup.okhttp.Credentials;
import com.squareup.okhttp.MediaType;
import com.squareup.okhttp.OkHttpClient;
@@ -18,16 +21,27 @@ import org.json.JSONObject;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
-import java.net.CookieManager;
-import java.net.CookiePolicy;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
+import java.security.KeyStore;
+import java.security.Principal;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Collection;
+import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
+import java.util.Map;
+
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLSocketFactory;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.TrustManagerFactory;
+import javax.net.ssl.X509TrustManager;
+import javax.security.auth.x500.X500Principal;
import de.danoeh.antennapod.core.ClientConfig;
import de.danoeh.antennapod.core.gpoddernet.model.GpodnetDevice;
@@ -43,6 +57,8 @@ import de.danoeh.antennapod.core.service.download.AntennapodHttpClient;
*/
public class GpodnetService {
+ private static final String TAG = "GpodnetService";
+
private static final String BASE_SCHEME = "https";
public static final String DEFAULT_BASE_HOST = "gpodder.net";
@@ -56,9 +72,84 @@ public class GpodnetService {
public GpodnetService() {
httpClient = AntennapodHttpClient.getHttpClient();
+ if (Build.VERSION.SDK_INT <= 10) {
+ Log.d(TAG, "Use custom SSL factory");
+ SSLSocketFactory factory = getCustomSslSocketFactory();
+ httpClient.setSslSocketFactory(factory);
+ }
BASE_HOST = GpodnetPreferences.getHostname();
}
+ private synchronized static SSLSocketFactory getCustomSslSocketFactory() {
+ try {
+ TrustManagerFactory defaultTrustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
+ defaultTrustManagerFactory.init((KeyStore) null); // use system keystore
+ final X509TrustManager defaultTrustManager = (X509TrustManager) defaultTrustManagerFactory.getTrustManagers()[0];
+ TrustManager[] customTrustManagers = new TrustManager[]{new X509TrustManager() {
+ @Override
+ public java.security.cert.X509Certificate[] getAcceptedIssuers() {
+ return null;
+ }
+ @Override
+ public void checkClientTrusted(java.security.cert.X509Certificate[] chain, String authType) throws CertificateException {
+ }
+ @Override
+ public void checkServerTrusted(X509Certificate[] chain, String authType)
+ throws CertificateException {
+ // chain may out of order - construct data structures to walk from server certificate to root certificate
+ Map<Principal, X509Certificate> certificates = new HashMap<Principal, X509Certificate>(chain.length - 1);
+ X509Certificate subject = null;
+ for (X509Certificate cert : chain) {
+ cert.checkValidity();
+ if (cert.getSubjectDN().toString().startsWith("CN=" + DEFAULT_BASE_HOST)) {
+ subject = cert;
+ } else {
+ certificates.put(cert.getSubjectDN(), cert);
+ }
+ }
+ if (subject == null) {
+ throw new CertificateException("Chain does not contain a certificate for " + DEFAULT_BASE_HOST);
+ }
+ // follow chain to root CA
+ while (certificates.get(subject.getIssuerDN()) != null) {
+ subject.checkValidity();
+ X509Certificate issuer = certificates.get(subject.getIssuerDN());
+ try {
+ subject.verify(issuer.getPublicKey());
+ } catch (Exception e) {
+ Log.d(TAG, "failed: " + issuer.getSubjectDN() + " -> " + subject.getSubjectDN());
+ throw new CertificateException("Could not verify certificate");
+ }
+ subject = issuer;
+ }
+ X500Principal rootAuthority = subject.getIssuerX500Principal();
+ boolean accepted = false;
+ for (X509Certificate cert :
+ defaultTrustManager.getAcceptedIssuers()) {
+ if (cert.getSubjectX500Principal().equals(rootAuthority)) {
+ try {
+ subject.verify(cert.getPublicKey());
+ accepted = true;
+ } catch (Exception e) {
+ Log.d(TAG, "failed: " + cert.getSubjectDN() + " -> " + subject.getSubjectDN());
+ throw new CertificateException("Could not verify root certificate");
+ }
+ }
+ }
+ if (accepted == false) {
+ throw new CertificateException("Could not verify root certificate");
+ }
+ }
+ }};
+ SSLContext sslContext = SSLContext.getInstance("TLS");
+ sslContext.init(null, customTrustManagers, null);
+ return sslContext.getSocketFactory();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ return null;
+ }
+
/**
* Returns the [count] most used tags.
*/
@@ -81,9 +172,10 @@ public class GpodnetService {
jsonTagList.length());
for (int i = 0; i < jsonTagList.length(); i++) {
JSONObject jObj = jsonTagList.getJSONObject(i);
- String name = jObj.getString("tag");
+ String title = jObj.getString("title");
+ String tag = jObj.getString("tag");
int usage = jObj.getInt("usage");
- tagList.add(new GpodnetTag(name, usage));
+ tagList.add(new GpodnetTag(title, tag, usage));
}
return tagList;
} catch (JSONException e) {
@@ -103,7 +195,7 @@ public class GpodnetService {
try {
URL url = new URI(BASE_SCHEME, BASE_HOST, String.format(
- "/api/2/tag/%s/%d.json", tag.getName(), count), null).toURL();
+ "/api/2/tag/%s/%d.json", tag.getTag(), count), null).toURL();
Request.Builder request = new Request.Builder().url(url);
String response = executeRequest(request);
diff --git a/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetTag.java b/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetTag.java
index 7178f4be5..cd865731b 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetTag.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetTag.java
@@ -1,46 +1,60 @@
package de.danoeh.antennapod.core.gpoddernet.model;
-import org.apache.commons.lang3.Validate;
+import android.os.Parcel;
+import android.os.Parcelable;
-import java.util.Comparator;
+import org.apache.commons.lang3.Validate;
-public class GpodnetTag {
+public class GpodnetTag implements Parcelable {
- private String name;
- private int usage;
+ private final String title;
+ private final String tag;
+ private final int usage;
- public GpodnetTag(String name, int usage) {
- Validate.notNull(name);
+ public GpodnetTag(String title, String tag, int usage) {
+ Validate.notNull(title);
+ Validate.notNull(tag);
- this.name = name;
+ this.title = title;
+ this.tag = tag;
this.usage = usage;
}
- public GpodnetTag(String name) {
- super();
- this.name = name;
+ public static GpodnetTag createFromParcel(Parcel in) {
+ final String title = in.readString();
+ final String tag = in.readString();
+ final int usage = in.readInt();
+ return new GpodnetTag(title, tag, usage);
}
@Override
public String toString() {
- return "GpodnetTag [name=" + name + ", usage=" + usage + "]";
+ return "GpodnetTag [title="+title+", tag=" + tag + ", usage=" + usage + "]";
}
- public String getName() {
- return name;
+ public String getTitle() {
+ return title;
+ }
+
+ public String getTag() {
+ return tag;
}
public int getUsage() {
return usage;
}
- public static class UsageComparator implements Comparator<GpodnetTag> {
-
- @Override
- public int compare(GpodnetTag o1, GpodnetTag o2) {
- return o1.usage - o2.usage;
- }
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(title);
+ dest.writeString(tag);
+ dest.writeInt(usage);
}
+
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/preferences/UserPreferences.java b/core/src/main/java/de/danoeh/antennapod/core/preferences/UserPreferences.java
index 7cbb69a7f..6cb2faba5 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/preferences/UserPreferences.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/preferences/UserPreferences.java
@@ -20,7 +20,6 @@ import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.TimeUnit;
-import de.danoeh.antennapod.core.ApplicationCallbacks;
import de.danoeh.antennapod.core.BuildConfig;
import de.danoeh.antennapod.core.ClientConfig;
import de.danoeh.antennapod.core.R;
@@ -42,6 +41,7 @@ public class UserPreferences implements
public static final String PREF_FOLLOW_QUEUE = "prefFollowQueue";
public static final String PREF_DOWNLOAD_MEDIA_ON_WIFI_ONLY = "prefDownloadMediaOnWifiOnly";
public static final String PREF_UPDATE_INTERVAL = "prefAutoUpdateIntervall";
+ public static final String PREF_PARALLEL_DOWNLOADS = "prefParallelDownloads";
public static final String PREF_MOBILE_UPDATE = "prefMobileUpdate";
public static final String PREF_DISPLAY_ONLY_EPISODES = "prefDisplayOnlyEpisodes";
public static final String PREF_AUTO_DELETE = "prefAutoDelete";
@@ -86,6 +86,7 @@ public class UserPreferences implements
private boolean enableAutodownloadWifiFilter;
private boolean enableAutodownloadOnBattery;
private String[] autodownloadSelectedNetworks;
+ private int parallelDownloads;
private int episodeCacheSize;
private String playbackSpeed;
private String[] playbackSpeedArray;
@@ -144,6 +145,7 @@ public class UserPreferences implements
PREF_ENABLE_AUTODL_WIFI_FILTER, false);
autodownloadSelectedNetworks = StringUtils.split(
sp.getString(PREF_AUTODL_SELECTED_NETWORKS, ""), ',');
+ parallelDownloads = Integer.valueOf(sp.getString(PREF_PARALLEL_DOWNLOADS, "6"));
episodeCacheSize = readEpisodeCacheSizeInternal(sp.getString(
PREF_EPISODE_CACHE_SIZE, "20"));
enableAutodownload = sp.getBoolean(PREF_ENABLE_AUTODL, false);
@@ -314,6 +316,11 @@ public class UserPreferences implements
return instance.autodownloadSelectedNetworks;
}
+ public static int getParallelDownloads() {
+ instanceAvailable();
+ return instance.parallelDownloads;
+ }
+
public static int getEpisodeCacheSizeUnlimited() {
return EPISODE_CACHE_SIZE_UNLIMITED;
}
@@ -399,6 +406,8 @@ public class UserPreferences implements
} else if (key.equals(PREF_AUTODL_SELECTED_NETWORKS)) {
autodownloadSelectedNetworks = StringUtils.split(
sp.getString(PREF_AUTODL_SELECTED_NETWORKS, ""), ',');
+ } else if(key.equals(PREF_PARALLEL_DOWNLOADS)) {
+ parallelDownloads = Integer.valueOf(sp.getString(PREF_PARALLEL_DOWNLOADS, "6"));
} else if (key.equals(PREF_EPISODE_CACHE_SIZE)) {
episodeCacheSize = readEpisodeCacheSizeInternal(sp.getString(
PREF_EPISODE_CACHE_SIZE, "20"));
diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadService.java b/core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadService.java
index 02a6aecbd..60d463178 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadService.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadService.java
@@ -52,7 +52,6 @@ import java.util.concurrent.atomic.AtomicInteger;
import javax.xml.parsers.ParserConfigurationException;
-import de.danoeh.antennapod.core.BuildConfig;
import de.danoeh.antennapod.core.ClientConfig;
import de.danoeh.antennapod.core.R;
import de.danoeh.antennapod.core.feed.EventDistributor;
@@ -61,6 +60,7 @@ import de.danoeh.antennapod.core.feed.FeedImage;
import de.danoeh.antennapod.core.feed.FeedItem;
import de.danoeh.antennapod.core.feed.FeedMedia;
import de.danoeh.antennapod.core.feed.FeedPreferences;
+import de.danoeh.antennapod.core.preferences.UserPreferences;
import de.danoeh.antennapod.core.storage.DBReader;
import de.danoeh.antennapod.core.storage.DBTasks;
import de.danoeh.antennapod.core.storage.DBWriter;
@@ -172,12 +172,11 @@ public class DownloadService extends Service {
@Override
public void run() {
- if (BuildConfig.DEBUG) Log.d(TAG, "downloadCompletionThread was started");
+ Log.d(TAG, "downloadCompletionThread was started");
while (!isInterrupted()) {
try {
Downloader downloader = downloadExecutor.take().get();
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Received 'Download Complete' - message.");
+ Log.d(TAG, "Received 'Download Complete' - message.");
removeDownload(downloader);
DownloadStatus status = downloader.getResult();
boolean successful = status.isSuccessful();
@@ -213,13 +212,13 @@ public class DownloadService extends Service {
queryDownloadsAsync();
}
} catch (InterruptedException e) {
- if (BuildConfig.DEBUG) Log.d(TAG, "DownloadCompletionThread was interrupted");
+ Log.d(TAG, "DownloadCompletionThread was interrupted");
} catch (ExecutionException e) {
e.printStackTrace();
numberOfDownloads.decrementAndGet();
}
}
- if (BuildConfig.DEBUG) Log.d(TAG, "End of downloadCompletionThread");
+ Log.d(TAG, "End of downloadCompletionThread");
}
};
@@ -236,8 +235,7 @@ public class DownloadService extends Service {
@SuppressLint("NewApi")
@Override
public void onCreate() {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Service started");
+ Log.d(TAG, "Service started");
isRunning = true;
handler = new Handler();
newMediaFiles = Collections.synchronizedList(new ArrayList<Long>());
@@ -258,8 +256,9 @@ public class DownloadService extends Service {
return t;
}
});
+ Log.d(TAG, "parallel downloads: " + UserPreferences.getParallelDownloads());
downloadExecutor = new ExecutorCompletionService<Downloader>(
- Executors.newFixedThreadPool(NUM_PARALLEL_DOWNLOADS,
+ Executors.newFixedThreadPool(UserPreferences.getParallelDownloads(),
new ThreadFactory() {
@Override
@@ -304,8 +303,7 @@ public class DownloadService extends Service {
@Override
public void onDestroy() {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Service shutting down");
+ Log.d(TAG, "Service shutting down");
isRunning = false;
if (ClientConfig.downloadServiceCallbacks.shouldCreateReport()) {
@@ -346,8 +344,7 @@ public class DownloadService extends Service {
.setLargeIcon(icon)
.setSmallIcon(R.drawable.stat_notify_sync);
}
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Notification set up");
+ Log.d(TAG, "Notification set up");
}
/**
@@ -427,8 +424,7 @@ public class DownloadService extends Service {
String url = intent.getStringExtra(EXTRA_DOWNLOAD_URL);
Validate.notNull(url, "ACTION_CANCEL_DOWNLOAD intent needs download url extra");
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Cancelling download with url " + url);
+ Log.d(TAG, "Cancelling download with url " + url);
Downloader d = getDownloader(url);
if (d != null) {
d.cancel();
@@ -439,8 +435,7 @@ public class DownloadService extends Service {
} else if (StringUtils.equals(intent.getAction(), ACTION_CANCEL_ALL_DOWNLOADS)) {
for (Downloader d : downloads) {
d.cancel();
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Cancelled all downloads");
+ Log.d(TAG, "Cancelled all downloads");
}
sendBroadcast(new Intent(ACTION_DOWNLOADS_CONTENT_CHANGED));
@@ -451,8 +446,7 @@ public class DownloadService extends Service {
};
private void onDownloadQueued(Intent intent) {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Received enqueue request");
+ Log.d(TAG, "Received enqueue request");
DownloadRequest request = intent.getParcelableExtra(EXTRA_REQUEST);
if (request == null) {
throw new IllegalArgumentException(
@@ -462,7 +456,12 @@ public class DownloadService extends Service {
Downloader downloader = getDownloader(request);
if (downloader != null) {
numberOfDownloads.incrementAndGet();
- downloads.add(downloader);
+ // smaller rss feeds before bigger media files
+ if(request.getFeedfileId() == Feed.FEEDFILETYPE_FEED) {
+ downloads.add(0, downloader);
+ } else {
+ downloads.add(downloader);
+ }
downloadExecutor.submit(downloader);
sendBroadcast(new Intent(ACTION_DOWNLOADS_CONTENT_CHANGED));
}
@@ -490,12 +489,10 @@ public class DownloadService extends Service {
handler.post(new Runnable() {
@Override
public void run() {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Removing downloader: "
- + d.getDownloadRequest().getSource());
+ Log.d(TAG, "Removing downloader: "
+ + d.getDownloadRequest().getSource());
boolean rc = downloads.remove(d);
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Result of downloads.remove: " + rc);
+ Log.d(TAG, "Result of downloads.remove: " + rc);
DownloadRequester.getInstance().removeDownload(d.getDownloadRequest());
sendBroadcast(new Intent(ACTION_DOWNLOADS_CONTENT_CHANGED));
}
@@ -544,8 +541,7 @@ public class DownloadService extends Service {
}
if (createReport) {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Creating report");
+ Log.d(TAG, "Creating report");
// create notification object
Notification notification = new NotificationCompat.Builder(this)
.setTicker(
@@ -569,8 +565,7 @@ public class DownloadService extends Service {
NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
nm.notify(REPORT_ID, notification);
} else {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "No report is created");
+ Log.d(TAG, "No report is created");
}
reportQueue.clear();
}
@@ -592,13 +587,10 @@ public class DownloadService extends Service {
* Check if there's something else to download, otherwise stop
*/
void queryDownloads() {
- if (BuildConfig.DEBUG) {
- Log.d(TAG, numberOfDownloads.get() + " downloads left");
- }
+ Log.d(TAG, numberOfDownloads.get() + " downloads left");
if (numberOfDownloads.get() <= 0 && DownloadRequester.getInstance().hasNoDownloads()) {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Number of downloads is " + numberOfDownloads.get() + ", attempting shutdown");
+ Log.d(TAG, "Number of downloads is " + numberOfDownloads.get() + ", attempting shutdown");
stopSelf();
} else {
setupNotificationUpdater();
@@ -634,8 +626,7 @@ public class DownloadService extends Service {
* Is called whenever a Feed is downloaded
*/
private void handleCompletedFeedDownload(DownloadRequest request) {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Handling completed Feed Download");
+ Log.d(TAG, "Handling completed Feed Download");
feedSyncThread.submitCompletedDownload(request);
}
@@ -644,8 +635,7 @@ public class DownloadService extends Service {
* Is called whenever a Feed-Image is downloaded
*/
private void handleCompletedImageDownload(DownloadStatus status, DownloadRequest request) {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Handling completed Image Download");
+ Log.d(TAG, "Handling completed Image Download");
syncExecutor.execute(new ImageHandlerThread(status, request));
}
@@ -653,13 +643,12 @@ public class DownloadService extends Service {
* Is called whenever a FeedMedia is downloaded.
*/
private void handleCompletedFeedMediaDownload(DownloadStatus status, DownloadRequest request) {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Handling completed FeedMedia Download");
+ Log.d(TAG, "Handling completed FeedMedia Download");
syncExecutor.execute(new MediaHandlerThread(status, request));
}
private void handleFailedDownload(DownloadStatus status, DownloadRequest request) {
- if (BuildConfig.DEBUG) Log.d(TAG, "Handling failed download");
+ Log.d(TAG, "Handling failed download");
syncExecutor.execute(new FailedDownloadHandler(status, request));
}
@@ -709,12 +698,10 @@ public class DownloadService extends Service {
long currentTime = startTime;
while (requester.isDownloadingFeeds() && (currentTime - startTime) < WAIT_TIMEOUT) {
try {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Waiting for " + (startTime + WAIT_TIMEOUT - currentTime) + " ms");
+ Log.d(TAG, "Waiting for " + (startTime + WAIT_TIMEOUT - currentTime) + " ms");
sleep(startTime + WAIT_TIMEOUT - currentTime);
} catch (InterruptedException e) {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "interrupted while waiting for more downloads");
+ Log.d(TAG, "interrupted while waiting for more downloads");
tasks += pollCompletedDownloads();
} finally {
currentTime = System.currentTimeMillis();
@@ -762,7 +749,7 @@ public class DownloadService extends Service {
continue;
}
- if (BuildConfig.DEBUG) Log.d(TAG, "Bundling " + results.size() + " feeds");
+ Log.d(TAG, "Bundling " + results.size() + " feeds");
for (Pair<DownloadRequest, FeedHandlerResult> result : results) {
removeDuplicateImages(result.second.feed); // duplicate images have to removed because the DownloadRequester does not accept two downloads with the same download URL yet.
@@ -789,8 +776,7 @@ public class DownloadService extends Service {
// Download Feed Image if provided and not downloaded
if (savedFeed.getImage() != null
&& savedFeed.getImage().isDownloaded() == false) {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Feed has image; Downloading....");
+ Log.d(TAG, "Feed has image; Downloading....");
savedFeed.getImage().setOwner(savedFeed);
final Feed savedFeedRef = savedFeed;
try {
@@ -856,7 +842,7 @@ public class DownloadService extends Service {
}
- if (BuildConfig.DEBUG) Log.d(TAG, "Shutting down");
+ Log.d(TAG, "Shutting down");
}
@@ -902,8 +888,7 @@ public class DownloadService extends Service {
FeedHandlerResult result = null;
try {
result = feedHandler.parseFeed(feed);
- if (BuildConfig.DEBUG)
- Log.d(TAG, feed.getTitle() + " parsed");
+ Log.d(TAG, feed.getTitle() + " parsed");
if (checkFeedData(feed) == false) {
throw new InvalidFeedException();
}
@@ -1008,13 +993,13 @@ public class DownloadService extends Service {
*/
private void cleanup(Feed feed) {
if (feed.getFile_url() != null) {
- if (new File(feed.getFile_url()).delete())
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Successfully deleted cache file.");
- else
- Log.e(TAG, "Failed to delete cache file.");
+ if (new File(feed.getFile_url()).delete()) {
+ Log.d(TAG, "Successfully deleted cache file.");
+ } else {
+ Log.e(TAG, "Failed to delete cache file.");
+ }
feed.setFile_url(null);
- } else if (BuildConfig.DEBUG) {
+ } else {
Log.d(TAG, "Didn't delete cache file: File url is not set.");
}
}
@@ -1056,7 +1041,7 @@ public class DownloadService extends Service {
@Override
public void run() {
if (request.isDeleteOnFailure()) {
- if (BuildConfig.DEBUG) Log.d(TAG, "Ignoring failed download, deleteOnFailure=true");
+ Log.d(TAG, "Ignoring failed download, deleteOnFailure=true");
} else {
File dest = new File(request.getDestination());
if (dest.exists() && request.getFeedfileType() == FeedMedia.FEEDFILETYPE_FEEDMEDIA) {
@@ -1144,8 +1129,7 @@ public class DownloadService extends Service {
mmr.setDataSource(media.getFile_url());
String durationStr = mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION);
media.setDuration(Integer.parseInt(durationStr));
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Duration of file is " + media.getDuration());
+ Log.d(TAG, "Duration of file is " + media.getDuration());
} catch (NumberFormatException e) {
e.printStackTrace();
} catch (RuntimeException e) {
@@ -1191,8 +1175,7 @@ public class DownloadService extends Service {
* Schedules the notification updater task if it hasn't been scheduled yet.
*/
private void setupNotificationUpdater() {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Setting up notification updater");
+ Log.d(TAG, "Setting up notification updater");
if (notificationUpdater == null) {
notificationUpdater = new NotificationUpdater();
notificationUpdaterFuture = schedExecutor.scheduleAtFixedRate(
diff --git a/core/src/main/java/de/danoeh/antennapod/core/syndication/handler/SyndHandler.java b/core/src/main/java/de/danoeh/antennapod/core/syndication/handler/SyndHandler.java
index 1dda24944..47503dee4 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/syndication/handler/SyndHandler.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/syndication/handler/SyndHandler.java
@@ -1,14 +1,23 @@
package de.danoeh.antennapod.core.syndication.handler;
import android.util.Log;
-import de.danoeh.antennapod.core.BuildConfig;
-import de.danoeh.antennapod.core.feed.Feed;
-import de.danoeh.antennapod.core.syndication.namespace.*;
-import de.danoeh.antennapod.core.syndication.namespace.atom.NSAtom;
+
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
+import de.danoeh.antennapod.core.BuildConfig;
+import de.danoeh.antennapod.core.feed.Feed;
+import de.danoeh.antennapod.core.syndication.namespace.NSContent;
+import de.danoeh.antennapod.core.syndication.namespace.NSDublinCore;
+import de.danoeh.antennapod.core.syndication.namespace.NSITunes;
+import de.danoeh.antennapod.core.syndication.namespace.NSMedia;
+import de.danoeh.antennapod.core.syndication.namespace.NSRSS20;
+import de.danoeh.antennapod.core.syndication.namespace.NSSimpleChapters;
+import de.danoeh.antennapod.core.syndication.namespace.Namespace;
+import de.danoeh.antennapod.core.syndication.namespace.SyndElement;
+import de.danoeh.antennapod.core.syndication.namespace.atom.NSAtom;
+
/** Superclass for all SAX Handlers which process Syndication formats */
public class SyndHandler extends DefaultHandler {
private static final String TAG = "SyndHandler";
@@ -100,7 +109,12 @@ public class SyndHandler extends DefaultHandler {
state.namespaces.put(uri, new NSMedia());
if (BuildConfig.DEBUG)
Log.d(TAG, "Recognized media namespace");
- }
+ } else if (uri.equals(NSDublinCore.NSURI)
+ && prefix.equals(NSDublinCore.NSTAG)) {
+ state.namespaces.put(uri, new NSDublinCore());
+ if (BuildConfig.DEBUG)
+ Log.d(TAG, "Recognized DublinCore namespace");
+ }
}
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/syndication/namespace/NSDublinCore.java b/core/src/main/java/de/danoeh/antennapod/core/syndication/namespace/NSDublinCore.java
new file mode 100644
index 000000000..099593eed
--- /dev/null
+++ b/core/src/main/java/de/danoeh/antennapod/core/syndication/namespace/NSDublinCore.java
@@ -0,0 +1,37 @@
+package de.danoeh.antennapod.core.syndication.namespace;
+
+import org.xml.sax.Attributes;
+
+import de.danoeh.antennapod.core.syndication.handler.HandlerState;
+import de.danoeh.antennapod.core.syndication.util.SyndDateUtils;
+
+public class NSDublinCore extends Namespace {
+ private static final String TAG = "NSDublinCore";
+ public static final String NSTAG = "dc";
+ public static final String NSURI = "http://purl.org/dc/elements/1.1/";
+
+ private static final String ITEM = "item";
+ private static final String DATE = "date";
+
+ @Override
+ public SyndElement handleElementStart(String localName, HandlerState state,
+ Attributes attributes) {
+ return new SyndElement(localName, this);
+ }
+
+ @Override
+ public void handleElementEnd(String localName, HandlerState state) {
+ if(state.getTagstack().size() >= 2
+ && state.getContentBuf() != null) {
+ String content = state.getContentBuf().toString();
+ SyndElement topElement = state.getTagstack().peek();
+ String top = topElement.getName();
+ SyndElement secondElement = state.getSecondTag();
+ String second = secondElement.getName();
+ if (top.equals(DATE) && second.equals(ITEM)) {
+ state.getCurrentItem().setPubDate(
+ SyndDateUtils.parseISO8601Date(content));
+ }
+ }
+ }
+}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/syndication/util/SyndDateUtils.java b/core/src/main/java/de/danoeh/antennapod/core/syndication/util/SyndDateUtils.java
index 1ac389f08..a9929d7b1 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/syndication/util/SyndDateUtils.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/syndication/util/SyndDateUtils.java
@@ -28,6 +28,8 @@ public class SyndDateUtils {
*/
public static final String RFC3339LOCAL = "yyyy-MM-dd'T'HH:mm:ssZ";
+ public static final String ISO8601_SHORT = "yyyy-MM-dd";
+
private static ThreadLocal<SimpleDateFormat> RFC822Formatter = new ThreadLocal<SimpleDateFormat>() {
@Override
protected SimpleDateFormat initialValue() {
@@ -44,6 +46,14 @@ public class SyndDateUtils {
};
+ private static ThreadLocal<SimpleDateFormat> ISO8601ShortFormatter = new ThreadLocal<SimpleDateFormat>() {
+ @Override
+ protected SimpleDateFormat initialValue() {
+ return new SimpleDateFormat(ISO8601_SHORT, Locale.US);
+ }
+
+ };
+
public static Date parseRFC822Date(String date) {
Date result = null;
if (date.contains("PDT")) {
@@ -123,6 +133,23 @@ public class SyndDateUtils {
}
+ public static Date parseISO8601Date(String date) {
+ if(date.length() > ISO8601_SHORT.length()) {
+ return parseRFC3339Date(date);
+ }
+ Date result = null;
+ if(date.length() == "YYYYMMDD".length()) {
+ date = date.substring(0, 4) + "-" + date.substring(4, 6) + "-" + date.substring(6,8);
+ }
+ SimpleDateFormat format = ISO8601ShortFormatter.get();
+ try {
+ result = format.parse(date);
+ } catch (ParseException e) {
+ e.printStackTrace();
+ }
+ return result;
+ }
+
/**
* Takes a string of the form [HH:]MM:SS[.mmm] and converts it to
* milliseconds.
diff --git a/core/src/main/res/drawable-hdpi/ic_feed_grey600_24dp.png b/core/src/main/res/drawable-hdpi/ic_feed_grey600_24dp.png
new file mode 100755
index 000000000..46be3e14e
--- /dev/null
+++ b/core/src/main/res/drawable-hdpi/ic_feed_grey600_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-hdpi/ic_feed_white_24dp.png b/core/src/main/res/drawable-hdpi/ic_feed_white_24dp.png
new file mode 100755
index 000000000..3d57127f5
--- /dev/null
+++ b/core/src/main/res/drawable-hdpi/ic_feed_white_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-mdpi/ic_feed_grey600_24dp.png b/core/src/main/res/drawable-mdpi/ic_feed_grey600_24dp.png
new file mode 100755
index 000000000..79f082610
--- /dev/null
+++ b/core/src/main/res/drawable-mdpi/ic_feed_grey600_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-mdpi/ic_feed_white_24dp.png b/core/src/main/res/drawable-mdpi/ic_feed_white_24dp.png
new file mode 100755
index 000000000..15a4b16bf
--- /dev/null
+++ b/core/src/main/res/drawable-mdpi/ic_feed_white_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xhdpi/ic_feed_grey600_24dp.png b/core/src/main/res/drawable-xhdpi/ic_feed_grey600_24dp.png
new file mode 100755
index 000000000..5cb0262ee
--- /dev/null
+++ b/core/src/main/res/drawable-xhdpi/ic_feed_grey600_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xhdpi/ic_feed_white_24dp.png b/core/src/main/res/drawable-xhdpi/ic_feed_white_24dp.png
new file mode 100755
index 000000000..5f34b0492
--- /dev/null
+++ b/core/src/main/res/drawable-xhdpi/ic_feed_white_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xxhdpi/ic_feed_grey600_24dp.png b/core/src/main/res/drawable-xxhdpi/ic_feed_grey600_24dp.png
new file mode 100755
index 000000000..01ef2ee4d
--- /dev/null
+++ b/core/src/main/res/drawable-xxhdpi/ic_feed_grey600_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xxhdpi/ic_feed_white_24dp.png b/core/src/main/res/drawable-xxhdpi/ic_feed_white_24dp.png
new file mode 100755
index 000000000..6dd465852
--- /dev/null
+++ b/core/src/main/res/drawable-xxhdpi/ic_feed_white_24dp.png
Binary files differ
diff --git a/core/src/main/res/values-zh-rCN/strings.xml b/core/src/main/res/values-zh-rCN/strings.xml
index 59958ec35..5d956bef4 100644
--- a/core/src/main/res/values-zh-rCN/strings.xml
+++ b/core/src/main/res/values-zh-rCN/strings.xml
@@ -3,11 +3,11 @@
<!--Activitiy and fragment titles-->
<string name="app_name">AntennaPod</string>
<string name="feeds_label">订阅</string>
- <string name="add_feed_label">添加博客</string>
+ <string name="add_feed_label">添加播客</string>
<string name="podcasts_label">播客</string>
- <string name="episodes_label">曲目</string>
- <string name="new_episodes_label">新曲目</string>
- <string name="all_episodes_label">所有曲目</string>
+ <string name="episodes_label">单集</string>
+ <string name="new_episodes_label">新单集</string>
+ <string name="all_episodes_label">所有单集</string>
<string name="new_label">最新</string>
<string name="waiting_list_label">等待列表</string>
<string name="settings_label">设置</string>
@@ -22,7 +22,7 @@
<string name="gpodnet_auth_label">gpodder.net 登录</string>
<!--New episodes fragment-->
<string name="recently_published_episodes_label">最近发布</string>
- <string name="episode_filter_label">仅显示新曲目</string>
+ <string name="episode_filter_label">仅显示新单集</string>
<!--Main activity-->
<string name="drawer_open">打开菜单</string>
<string name="drawer_close">关闭菜单</string>
@@ -47,7 +47,7 @@
<string name="chapters_label">章节</string>
<string name="shownotes_label">笔记</string>
<string name="description_label">描述</string>
- <string name="most_recent_prefix">最近曲目:\u0020</string>
+ <string name="most_recent_prefix">最近单集:\u0020</string>
<string name="episodes_suffix">\u0020 曲</string>
<string name="length_prefix">长度:\u0020</string>
<string name="size_prefix">大小:\u0020</string>
@@ -64,12 +64,12 @@
<string name="browse_gpoddernet_label">浏览 gpodder.net</string>
<!--Actions on feeds-->
<string name="mark_all_read_label">全部标识已读</string>
- <string name="mark_all_read_msg">将所有曲目标记为已读</string>
+ <string name="mark_all_read_msg">将所有单集标记为已读</string>
<string name="show_info_label">查看信息</string>
<string name="remove_feed_label">删除播客</string>
<string name="share_link_label">分享网站链接</string>
<string name="share_source_label">分享订阅链接</string>
- <string name="feed_delete_confirmation_msg">确认要删除这些订阅吗? 该订阅所有已经下载的曲目将一并删除. </string>
+ <string name="feed_delete_confirmation_msg">确认要删除这些订阅吗? 该订阅所有已经下载的单集将一并删除. </string>
<string name="feed_remover_msg">删除订阅</string>
<!--actions on feeditems-->
<string name="download_label">下载</string>
@@ -77,7 +77,7 @@
<string name="pause_label">暂停</string>
<string name="stream_label">流媒体</string>
<string name="remove_label">删除</string>
- <string name="remove_episode_lable">移除曲目</string>
+ <string name="remove_episode_lable">移除单集</string>
<string name="mark_read_label">标记已读</string>
<string name="mark_unread_label">标记未读</string>
<string name="add_to_queue_label">添加到播放列表</string>
@@ -86,7 +86,7 @@
<string name="support_label">Flattr 他</string>
<string name="enqueue_all_new">全部添加到播放列表</string>
<string name="download_all">全部下载</string>
- <string name="skip_episode_label">跳过曲目</string>
+ <string name="skip_episode_label">跳过单集</string>
<!--Download messages and labels-->
<string name="download_successful">成功</string>
<string name="download_failed">失败</string>
@@ -105,7 +105,7 @@
<string name="cancel_all_downloads_label">取消所有下载</string>
<string name="download_cancelled_msg">已取消下载</string>
<string name="download_report_title">下载完成</string>
- <string name="download_error_malformed_url">畸形 URL</string>
+ <string name="download_error_malformed_url">错误的 URL</string>
<string name="download_error_io_error">IO 错误</string>
<string name="download_error_request_error">请求出错</string>
<string name="download_error_db_access">数据库访问错误</string>
@@ -190,10 +190,10 @@
<string name="pref_set_theme_title">主题选择</string>
<string name="pref_set_theme_sum">改变 AntennaPod 外观</string>
<string name="pref_automatic_download_title">自动下载</string>
- <string name="pref_automatic_download_sum">配置自动下载的曲目</string>
+ <string name="pref_automatic_download_sum">配置自动下载的单集</string>
<string name="pref_autodl_wifi_filter_title">打开 Wi-Fi 过滤器</string>
<string name="pref_autodl_wifi_filter_sum">只允许在 Wi-Fi 网络下自动下载</string>
- <string name="pref_episode_cache_title">曲目缓存</string>
+ <string name="pref_episode_cache_title">单集缓存</string>
<string name="pref_theme_title_light">浅色</string>
<string name="pref_theme_title_dark">暗色</string>
<string name="pref_episode_cache_unlimited">无限</string>
@@ -210,9 +210,20 @@
<string name="pref_playback_speed_sum">自定义音频播放速度</string>
<string name="pref_gpodnet_sethostname_title">设置主机名</string>
<string name="pref_gpodnet_sethostname_use_default_host">使用默认主机</string>
+ <string name="pref_expandNotify_title">扩展通知</string>
+ <string name="pref_expandNotify_sum">总是扩展通知以显示播放按钮</string>
+ <string name="pref_persistNotify_title">保持播放控制</string>
+ <string name="pref_persistNotify_sum">在暂停时保持通知和锁屏界面的控制。</string>
+ <string name="pref_expand_notify_unsupport_toast">Android 版本 4.1 之前不支持扩展通知</string>
+ <string name="pref_seek_delta_title">定位时间</string>
+ <string name="pref_seek_delta_sum">当倒退或快速回放时以这些秒为单位</string>
+ <string name="pref_auto_delete_sum">当播放完成后删除单集</string>
+ <string name="pref_auto_delete_title">自动删除</string>
+ <string name="pref_unpauseOnHeadsetReconnect_title">耳机重新连接</string>
+ <string name="pref_unpauseOnHeadsetReconnect_sum">当耳机重新连接时恢复播放</string>
<!--Auto-Flattr dialog-->
<!--Search-->
- <string name="search_hint">搜索订阅或者曲目</string>
+ <string name="search_hint">搜索订阅或者单集</string>
<string name="found_in_shownotes_label">笔记中查找</string>
<string name="found_in_chapters_label">章节中查找</string>
<string name="search_status_no_results">没有找到任何结果</string>
@@ -300,17 +311,17 @@
<string name="media_type_video_label">视频</string>
<string name="navigate_upwards_label">向上导航</string>
<string name="butAction_label">更多动作</string>
- <string name="status_playing_label">曲目正在播放</string>
- <string name="status_downloading_label">曲目正在下载</string>
- <string name="status_downloaded_label">曲目已下载</string>
+ <string name="status_playing_label">单集正在播放</string>
+ <string name="status_downloading_label">单集正在下载</string>
+ <string name="status_downloaded_label">单集已下载</string>
<string name="status_unread_label">新项目</string>
- <string name="in_queue_label">曲目已经在播放列表中</string>
- <string name="new_episodes_count_label">新曲目数</string>
- <string name="in_progress_episodes_count_label">已收听曲目数</string>
+ <string name="in_queue_label">单集已经在播放列表中</string>
+ <string name="new_episodes_count_label">新单集数</string>
+ <string name="in_progress_episodes_count_label">已收听单集数</string>
<string name="drag_handle_content_description">拖动以变更本项目的位置</string>
<!--Feed information screen-->
<string name="authentication_label">验证</string>
- <string name="authentication_descr">给本播客及曲目变更用户名及密码</string>
+ <string name="authentication_descr">给本播客及单集变更用户名及密码</string>
<!--AntennaPodSP-->
<string name="sp_apps_importing_feeds_msg">正在从选定的应用中导入订阅...</string>
</resources>
diff --git a/core/src/main/res/values/attrs.xml b/core/src/main/res/values/attrs.xml
index f36119c8d..368921f76 100644
--- a/core/src/main/res/values/attrs.xml
+++ b/core/src/main/res/values/attrs.xml
@@ -11,6 +11,7 @@
<attr name="av_rewind" format="reference"/>
<attr name="content_discard" format="reference"/>
<attr name="content_new" format="reference"/>
+ <attr name="feed" format="reference"/>
<attr name="device_access_time" format="reference"/>
<attr name="location_web_site" format="reference"/>
<attr name="navigation_accept" format="reference"/>
diff --git a/core/src/main/res/values/strings.xml b/core/src/main/res/values/strings.xml
index e8c3408b2..72e7a2b31 100644
--- a/core/src/main/res/values/strings.xml
+++ b/core/src/main/res/values/strings.xml
@@ -67,13 +67,14 @@
<string name="close_label">Close</string>
<string name="retry_label">Retry</string>
<string name="auto_download_label">Include in auto downloads</string>
+ <string name="parallel_downloads_suffix">\u0020parallel downloads</string>
<!-- 'Add Feed' Activity labels -->
<string name="feedurl_label">Feed URL</string>
<string name="etxtFeedurlHint">URL of feed or website</string>
<string name="txtvfeedurl_label">Add Podcast by URL</string>
<string name="podcastdirectories_label">Find podcast in directory</string>
- <string name="podcastdirectories_descr">You can search for new podcasts by name, category or popularity in the gpodder.net directory.</string>
+ <string name="podcastdirectories_descr">You can search for new podcasts by name, category or popularity in the gpodder.net directory, or search the iTunes store.</string>
<string name="browse_gpoddernet_label">Browse gpodder.net</string>
<!-- Actions on feeds -->
@@ -248,6 +249,7 @@
<string name="pref_autodl_wifi_filter_sum">Allow automatic download only for selected Wi-Fi networks.</string>
<string name="pref_automatic_download_on_battery_title">Download when not charging</string>
<string name="pref_automatic_download_on_battery_sum">Allow automatic download when the battery is not charging</string>
+ <string name="pref_parallel_downloads_title">Parallel downloads</string>
<string name="pref_episode_cache_title">Episode cache</string>
<string name="pref_theme_title_light">Light</string>
<string name="pref_theme_title_dark">Dark</string>
@@ -396,4 +398,5 @@
<!-- AntennaPodSP -->
<string name="sp_apps_importing_feeds_msg">Importing subscriptions from single-purpose apps&#8230;</string>
+ <string name="search_itunes_label">Search iTunes</string>
</resources>
diff --git a/core/src/main/res/values/styles.xml b/core/src/main/res/values/styles.xml
index a2f180395..e8b0e2b2b 100644
--- a/core/src/main/res/values/styles.xml
+++ b/core/src/main/res/values/styles.xml
@@ -15,6 +15,7 @@
<item name="attr/content_discard">@drawable/ic_delete_grey600_24dp</item>
<item name="attr/content_new">@drawable/ic_add_grey600_24dp</item>
<item name="attr/device_access_time">@drawable/ic_timer_grey600_24dp</item>
+ <item name="attr/feed">@drawable/ic_feed_grey600_24dp</item>
<item name="attr/location_web_site">@drawable/ic_web_grey600_24dp</item>
<item name="attr/navigation_accept">@drawable/ic_done_grey600_24dp</item>
<item name="attr/navigation_cancel">@drawable/ic_cancel_grey600_24dp</item>
@@ -56,6 +57,7 @@
<item name="attr/content_discard">@drawable/ic_delete_white_24dp</item>
<item name="attr/content_new">@drawable/ic_add_white_24dp</item>
<item name="attr/device_access_time">@drawable/ic_timer_white_24dp</item>
+ <item name="attr/feed">@drawable/ic_feed_white_24dp</item>
<item name="attr/location_web_site">@drawable/ic_web_white_24dp</item>
<item name="attr/navigation_accept">@drawable/ic_done_white_24dp</item>
<item name="attr/navigation_cancel">@drawable/ic_cancel_white_24dp</item>
@@ -100,6 +102,7 @@
<item name="attr/content_discard">@drawable/ic_delete_grey600_24dp</item>
<item name="attr/content_new">@drawable/ic_add_grey600_24dp</item>
<item name="attr/device_access_time">@drawable/ic_timer_grey600_24dp</item>
+ <item name="attr/feed">@drawable/ic_feed_grey600_24dp</item>
<item name="attr/location_web_site">@drawable/ic_web_grey600_24dp</item>
<item name="attr/navigation_accept">@drawable/ic_done_grey600_24dp</item>
<item name="attr/navigation_cancel">@drawable/ic_cancel_grey600_24dp</item>
@@ -143,6 +146,7 @@
<item name="attr/content_discard">@drawable/ic_delete_white_24dp</item>
<item name="attr/content_new">@drawable/ic_add_white_24dp</item>
<item name="attr/device_access_time">@drawable/ic_timer_white_24dp</item>
+ <item name="attr/feed">@drawable/ic_feed_white_24dp</item>
<item name="attr/location_web_site">@drawable/ic_web_white_24dp</item>
<item name="attr/navigation_accept">@drawable/ic_done_white_24dp</item>
<item name="attr/navigation_cancel">@drawable/ic_cancel_white_24dp</item>