package net.maxbraun.mirror; import android.util.Log; import android.util.Xml; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import java.io.IOException; import java.io.StringReader; import java.util.ArrayList; import java.util.List; import java.util.concurrent.TimeUnit; /** * A helper class to regularly retrieve news headlines. */ public class News extends DataUpdater<List<String>> { private static final String TAG = News.class.getSimpleName(); /** * The "Top Headlines" news feed from the Associated Press. */ private static final String AP_TOP_HEADLINES_URL = "http://hosted2.ap.org/atom/APDEFAULT/3d281c11a96b4ad082fe88aa0db04305"; /** * The time in milliseconds between API calls to update the news. */ private static final long UPDATE_INTERVAL_MILLIS = TimeUnit.MINUTES.toMillis(15); /** * A parser for the news feed XML. */ private final XmlPullParser parser; public News(UpdateListener<List<String>> updateListener) { super(updateListener, UPDATE_INTERVAL_MILLIS); parser = Xml.newPullParser(); try { parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, false); } catch (XmlPullParserException e) { Log.e(TAG, "Failed to initialize XML parser.", e); } } @Override protected List<String> getData() { // Get the latest headlines from the AP news feed. String response = Network.get(AP_TOP_HEADLINES_URL); if (response == null) { Log.w(TAG, "Empty response."); return null; } // Parse just the headlines from the XML. try { parser.setInput(new StringReader(response)); parser.nextTag(); parser.require(XmlPullParser.START_TAG, null, "feed"); List<String> headlines = new ArrayList<>(); while (parser.next() != XmlPullParser.END_TAG) { if (parser.getEventType() != XmlPullParser.START_TAG) { continue; } if (parser.getName().equals("entry")) { String title = readEntryTitle(); if (title != null) { headlines.add(title); } } else { skipTags(); } } return headlines; } catch (IOException | XmlPullParserException e) { Log.e(TAG, "Parsing news XML response failed: " + response, e); return null; } } /** * Reads the contents of a {@code <title>} tag within an {@code <entry} tag at the current parser * position. */ private String readEntryTitle() throws IOException, XmlPullParserException { parser.require(XmlPullParser.START_TAG, null, "entry"); String title = null; while (parser.next() != XmlPullParser.END_TAG) { if (parser.getEventType() != XmlPullParser.START_TAG) { continue; } if (parser.getName().equals("title")) { title = readText("title"); } else { skipTags(); } } return title; } /** * Reads the contents of a tag by the specified name at the current parser position. */ private String readText(String name) throws IOException, XmlPullParserException { parser.require(XmlPullParser.START_TAG, null, name); String text = ""; if (parser.next() == XmlPullParser.TEXT) { text = parser.getText(); parser.nextTag(); } parser.require(XmlPullParser.END_TAG, null, name); return text; } /** * Skips tags from the current parser position until the starting one is closed. */ private void skipTags() throws IOException, XmlPullParserException { if (parser.getEventType() != XmlPullParser.START_TAG) { throw new IllegalStateException("Not skipping from a start tag."); } int depth = 1; while (depth != 0) { switch (parser.next()) { case XmlPullParser.END_TAG: depth--; break; case XmlPullParser.START_TAG: depth++; break; } } } @Override protected String getTag() { return TAG; } }