diff --git a/src/net/i2p/android/apps/NewsFetcher.java b/src/net/i2p/android/apps/NewsFetcher.java
new file mode 100644
index 0000000000000000000000000000000000000000..b69f05977200f36f697b310eb37a5b77751ea30a
--- /dev/null
+++ b/src/net/i2p/android/apps/NewsFetcher.java
@@ -0,0 +1,199 @@
+package net.i2p.android.apps;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.List;
+
+import net.i2p.data.DataHelper;
+import net.i2p.router.Router;
+import net.i2p.router.RouterContext;
+import net.i2p.router.util.RFC822Date;
+import net.i2p.util.EepGet;
+import net.i2p.util.FileUtil;
+import net.i2p.util.Log;
+import net.i2p.util.Translate;
+
+/**
+ * From router console, simplified since we don't deal with router versions
+ * or updates.
+ */
+public class NewsFetcher implements Runnable, EepGet.StatusListener {
+    private final RouterContext _context;
+    private final Log _log;
+    private long _lastFetch;
+    private long _lastUpdated;
+    private String _lastModified;
+    private boolean _invalidated;
+    private File _newsFile;
+    private File _tempFile;
+
+    private static final String NEWS_FILE = "docs/news.xml";
+    private static final String TEMP_NEWS_FILE = "news.xml.temp";
+    /** @since 0.7.14 not configurable */
+    private static final String BACKUP_NEWS_URL = "http://www.i2p2.i2p/_static/news/news.xml";
+    private static final String PROP_LAST_CHECKED = "router.newsLastChecked";
+    private static final String PROP_REFRESH_FREQUENCY = "router.newsRefreshFrequency";
+    private static final String DEFAULT_REFRESH_FREQUENCY = 24*60*60*1000 + "";
+    private static final String PROP_NEWS_URL = "router.newsURL";
+    private static final String DEFAULT_NEWS_URL = "http://echelon.i2p/i2p/news.xml";
+    
+    private NewsFetcher(RouterContext ctx) {
+        _context = ctx;
+        _log = ctx.logManager().getLog(NewsFetcher.class);
+        try {
+            String last = ctx.getProperty(PROP_LAST_CHECKED);
+            if (last != null)
+                _lastFetch = Long.parseLong(last);
+        } catch (NumberFormatException nfe) {}
+        _newsFile = new File(_context.getRouterDir(), NEWS_FILE);
+        _tempFile = new File(_context.getTempDir(), TEMP_NEWS_FILE);
+        updateLastFetched();
+    }
+    
+    private void updateLastFetched() {
+        if (_newsFile.exists()) {
+            if (_lastUpdated == 0)
+                _lastUpdated = _newsFile.lastModified();
+            if (_lastFetch == 0)
+                _lastFetch = _lastUpdated;
+            if (_lastModified == null)
+                _lastModified = RFC822Date.to822Date(_lastFetch);
+        } else {
+            _lastUpdated = 0;
+            _lastFetch = 0;
+            _lastModified = null;
+        }
+    }
+    
+    public String status() {
+         StringBuilder buf = new StringBuilder(128);
+         long now = _context.clock().now();
+         if (_lastUpdated > 0) {
+             buf.append(Translate.getString("News last updated {0} ago.",
+                                           DataHelper.formatDuration2(now - _lastUpdated),
+                                           _context, "foo"))
+                .append('\n');
+         }
+         if (_lastFetch > _lastUpdated) {
+             buf.append(Translate.getString("News last checked {0} ago.",
+                                           DataHelper.formatDuration2(now - _lastFetch),
+                                           _context, "foo"));
+         }
+         return buf.toString();
+    }
+    
+    private static final long INITIAL_DELAY = 5*60*1000;
+    private static final long RUN_DELAY = 10*60*1000;
+
+    public void run() {
+        try { Thread.sleep(INITIAL_DELAY + _context.random().nextLong(INITIAL_DELAY)); } catch (InterruptedException ie) {}
+        while (true) {
+            if (shouldFetchNews()) {
+                fetchNews();
+            }
+            try { Thread.sleep(RUN_DELAY); } catch (InterruptedException ie) {}
+        }
+    }
+    
+    private boolean shouldFetchNews() {
+        if (_invalidated)
+            return true;
+        updateLastFetched();
+        String freq = _context.getProperty(PROP_REFRESH_FREQUENCY,
+                                           DEFAULT_REFRESH_FREQUENCY);
+        try {
+            long ms = Long.parseLong(freq);
+            if (ms <= 0)
+                return false;
+            
+            if (_lastFetch + ms < _context.clock().now()) {
+                return true;
+            } else {
+                if (_log.shouldLog(Log.DEBUG))
+                    _log.debug("Last fetched " + DataHelper.formatDuration(_context.clock().now() - _lastFetch) + " ago");
+                return false;
+            }
+        } catch (NumberFormatException nfe) {
+            if (_log.shouldLog(Log.ERROR))
+                _log.error("Invalid refresh frequency: " + freq);
+            return false;
+        }
+    }
+
+    /**
+     *  Call this when changing news URLs to force an update next time the timer fires.
+     *  @since 0.8.7
+     */
+    void invalidateNews() {
+        _lastModified = null;
+        _invalidated = true;
+    }
+
+    public void fetchNews() {
+        String newsURL = _context.getProperty(PROP_NEWS_URL, DEFAULT_NEWS_URL);
+        String proxyHost = "127.0.0.1";
+        int proxyPort = 4444;
+        if (_tempFile.exists())
+            _tempFile.delete();
+        
+        try {
+            EepGet get = null;
+            get = new EepGet(_context, true, proxyHost, proxyPort, 0, _tempFile.getAbsolutePath(), newsURL, true, null, _lastModified);
+            get.addStatusListener(this);
+            if (get.fetch()) {
+                _lastModified = get.getLastModified();
+                _invalidated = false;
+            } else {
+                // backup news location - always proxied
+                _tempFile.delete();
+                get = new EepGet(_context, true, proxyHost, proxyPort, 0, _tempFile.getAbsolutePath(), BACKUP_NEWS_URL, true, null, _lastModified);
+                get.addStatusListener(this);
+                if (get.fetch())
+                    _lastModified = get.getLastModified();
+            }
+        } catch (Throwable t) {
+            _log.error("Error fetching the news", t);
+        }
+    }
+    
+    public void attemptFailed(String url, long bytesTransferred, long bytesRemaining, int currentAttempt, int numRetries, Exception cause) {
+        // ignore
+    }
+    public void bytesTransferred(long alreadyTransferred, int currentWrite, long bytesTransferred, long bytesRemaining, String url) {
+        // ignore
+    }
+    public void transferComplete(long alreadyTransferred, long bytesTransferred, long bytesRemaining, String url, String outputFile, boolean notModified) {
+        if (_log.shouldLog(Log.INFO))
+            _log.info("News fetched from " + url + " with " + (alreadyTransferred+bytesTransferred));
+        
+        long now = _context.clock().now();
+        if (_tempFile.exists()) {
+            boolean copied = FileUtil.copy(_tempFile.getAbsolutePath(), _newsFile.getAbsolutePath(), true);
+            if (copied) {
+                _lastUpdated = now;
+                _tempFile.delete();
+                // notify somebody?
+            } else {
+                if (_log.shouldLog(Log.ERROR))
+                    _log.error("Failed to copy the news file!");
+            }
+        } else {
+            if (_log.shouldLog(Log.WARN))
+                _log.warn("Transfer complete, but no file? - probably 304 Not Modified");
+        }
+        _lastFetch = now;
+        _context.router().setConfigSetting(PROP_LAST_CHECKED, "" + now);
+        _context.router().saveConfig();
+    }
+    
+    public void transferFailed(String url, long bytesTransferred, long bytesRemaining, int currentAttempt) {
+        if (_log.shouldLog(Log.WARN))
+            _log.warn("Failed to fetch the news from " + url);
+        _tempFile.delete();
+    }
+    public void headerReceived(String url, int attemptNum, String key, String val) {}
+    public void attempting(String url) {}
+}