diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index cd0494739c9c563038df6b3aabf3c705ebe16491..8c3d32be53a42b3972011d13702d059f13721971 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -1,8 +1,8 @@
 <?xml version="1.0" encoding="utf-8"?>
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
       package="net.i2p.android.router"
-      android.versionCode="0"
-      android.versionName="0.0.0"
+      android.versionCode="4206788"
+      android.versionName="0.8.6-24_b132-SDK3"
       android:installLocation="preferExternal"
       >
     <uses-permission android:name="android.permission.INTERNET" />
diff --git a/build.xml b/build.xml
index 1adfe98a56d73aeaaa623fd73ac260a46efd6bd7..1d9789711a1523b36f078de05ee5cdf9774b75f7 100644
--- a/build.xml
+++ b/build.xml
@@ -78,7 +78,10 @@
 
     <!-- overrides of those in main_rules.xml -->
 
-    <target name="-pre-build" depends="copy-i2p-resources, incrementBuild" />
+    <target name="-pre-build" depends="copy-i2p-resources, incrementBuild" >
+        <!-- aapt messes up when resources are added or deleted, just build every time -->
+        <delete dir="${gen.absolute.dir}/net" verbose="${verbose}" />
+    </target>
 
     <target name="-pre-compile" depends="buildrouter" />
 
diff --git a/res/layout/main.xml b/res/layout/main.xml
index 2fc2a9a166db2075b77f36b1d5c5b18398f8ebef..8a8b089d242f579c0b62ab84f89ef10f732a493c 100644
--- a/res/layout/main.xml
+++ b/res/layout/main.xml
@@ -7,7 +7,7 @@
 <TextView  
     android:layout_width="fill_parent" 
     android:layout_height="wrap_content" 
-    android:text="I2P News Page"
+    android:text="I2P Main Page"
     />
 <ImageView  
     android:layout_width="fill_parent" 
diff --git a/res/layout/news.xml b/res/layout/news.xml
index ff88924ab0325f20e237ca544c1af1b62b42bada..8b640536c628e6eff46cef73be434061bc6029be 100644
--- a/res/layout/news.xml
+++ b/res/layout/news.xml
@@ -5,14 +5,11 @@
     android:layout_height="fill_parent"
     >
 <TextView  
+    android:id="@+id/news_status"
     android:layout_width="fill_parent" 
     android:layout_height="wrap_content" 
-    android:text="Hello World, I2PAndroid"
-    />
-<ImageView  
-    android:layout_width="fill_parent" 
-    android:layout_height="wrap_content" 
-    android:src="@drawable/i2plogo"
+    android:layout_centerHorizontal="true" 
+    android:text="Latest I2P News"
     />
 <WebView  
     android:id="@+id/news_webview"
diff --git a/res/raw/router_config b/res/raw/router_config
index c6fd9cba6ad359dd433b8257b2cffb2af7a2eadc..a48e139e0bb56297bdb128ec12bd9b8b47cef1db 100644
--- a/res/raw/router_config
+++ b/res/raw/router_config
@@ -1,7 +1,7 @@
 # initial router.config
-# temp directory
-i2p.dir.temp=/data/data/net.i2p.android.router/files/tmp
-i2p.dir.pid=/data/data/net.i2p.android.router/files/tmp
+# temp directory now set in Init.java
+#i2p.dir.temp=/data/data/net.i2p.android.router/files/tmp
+#i2p.dir.pid=/data/data/net.i2p.android.router/files/tmp
 # save memory
 prng.buffers=2
 prng.bufferSize=32768
diff --git a/src/net/i2p/android/apps/NewsFetcher.java b/src/net/i2p/android/apps/NewsFetcher.java
index b69f05977200f36f697b310eb37a5b77751ea30a..6ce2af50e8308a13ac62888823a4161cb446b92f 100644
--- a/src/net/i2p/android/apps/NewsFetcher.java
+++ b/src/net/i2p/android/apps/NewsFetcher.java
@@ -29,8 +29,21 @@ public class NewsFetcher implements Runnable, EepGet.StatusListener {
     private boolean _invalidated;
     private File _newsFile;
     private File _tempFile;
+    private static NewsFetcher _instance;
 
-    private static final String NEWS_FILE = "docs/news.xml";
+    public static final NewsFetcher getInstance() { 
+        return _instance;
+    }
+
+    public static final synchronized NewsFetcher getInstance(RouterContext ctx) { 
+        if (_instance != null)
+            return _instance;
+        _instance = new NewsFetcher(ctx);
+        return _instance;
+    }
+
+    private static final String NEWS_DIR = "docs";
+    private static final String NEWS_FILE = "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";
@@ -48,7 +61,10 @@ public class NewsFetcher implements Runnable, EepGet.StatusListener {
             if (last != null)
                 _lastFetch = Long.parseLong(last);
         } catch (NumberFormatException nfe) {}
-        _newsFile = new File(_context.getRouterDir(), NEWS_FILE);
+        File newsDir = new File(_context.getRouterDir(), NEWS_DIR);
+        // isn't already there on android
+        newsDir.mkdir();
+        _newsFile = new File(newsDir, NEWS_FILE);
         _tempFile = new File(_context.getTempDir(), TEMP_NEWS_FILE);
         updateLastFetched();
     }
@@ -86,15 +102,23 @@ public class NewsFetcher implements Runnable, EepGet.StatusListener {
     }
     
     private static final long INITIAL_DELAY = 5*60*1000;
-    private static final long RUN_DELAY = 10*60*1000;
+    private static final long RUN_DELAY = 30*60*1000;
 
     public void run() {
-        try { Thread.sleep(INITIAL_DELAY + _context.random().nextLong(INITIAL_DELAY)); } catch (InterruptedException ie) {}
+        try {
+            Thread.sleep(INITIAL_DELAY);
+        } catch (InterruptedException ie) {
+            return;
+        }
         while (true) {
             if (shouldFetchNews()) {
                 fetchNews();
             }
-            try { Thread.sleep(RUN_DELAY); } catch (InterruptedException ie) {}
+            try {
+                Thread.sleep(RUN_DELAY);
+            } catch (InterruptedException ie) {
+                break;
+            }
         }
     }
     
diff --git a/src/net/i2p/android/router/activity/NewsActivity.java b/src/net/i2p/android/router/activity/NewsActivity.java
index 19ed5f0fbaa2705d8937c4612fb07335c947b8d9..bfee330c8414c9d5d177fbe4acace4e7f74b78ac 100644
--- a/src/net/i2p/android/router/activity/NewsActivity.java
+++ b/src/net/i2p/android/router/activity/NewsActivity.java
@@ -2,39 +2,81 @@ package net.i2p.android.router.activity;
 
 import android.content.res.Resources;
 import android.os.Bundle;
+import android.view.KeyEvent;
 import android.webkit.WebView;
+import android.webkit.WebViewClient;
+import android.widget.TextView;
 
 import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
 import java.io.InputStream;
 import java.io.IOException;
 import java.io.UnsupportedEncodingException;
+import java.net.URI;
+import java.net.URISyntaxException;
 
 import net.i2p.android.router.R;
+import net.i2p.android.apps.NewsFetcher;
 
 public class NewsActivity extends I2PActivityBase {
 
+    private long _lastChanged;
+
+    // TODO add some inline style
+    private static final String HEADER = "<html><head></head><body>";
+    private static final String FOOTER = "</body></html>";
+    private static final String ERROR_EEPSITE = HEADER + "Sorry, eepsites not yet supported" + FOOTER;
+
     @Override
     public void onCreate(Bundle savedInstanceState)
     {
         super.onCreate(savedInstanceState);
         setContentView(R.layout.news);
         WebView wv = (WebView) findViewById(R.id.news_webview);
-        if (wv == null) {
-            System.err.println("No webview resource!");
-            return;
+        wv.setWebViewClient(new NewsWebViewClient());
+    }
+
+    @Override
+    public void onResume()
+    {
+        super.onResume();
+        NewsFetcher nf = NewsFetcher.getInstance();
+        if (nf != null) {
+            // always update the text
+            TextView tv = (TextView) findViewById(R.id.news_status);
+            tv.setText(nf.status().replace("&nbsp;", " "));
         }
 
+        // only update the webview if we need to
+        File newsFile = new File(_myDir, "docs/news.xml");
+        boolean newsExists = newsFile.exists();
+        if (_lastChanged > 0 && ((!newsExists) || newsFile.lastModified() < _lastChanged))
+            return;
+        _lastChanged = System.currentTimeMillis();
+
+        WebView wv = (WebView) findViewById(R.id.news_webview);
+
         InputStream in = null;
-        ByteArrayOutputStream out = new ByteArrayOutputStream(1024);
+        ByteArrayOutputStream out = new ByteArrayOutputStream(2048);
         byte buf[] = new byte[1024];
         try {
-            in = getResources().openRawResource(R.raw.initialnews_html);
+            if (newsExists) {
+                out.write(HEADER.getBytes());
+                in = new FileInputStream(newsFile);
+            } else {
+                in = getResources().openRawResource(R.raw.initialnews_html);
+            }
             
             int read = 0;
             while ( (read = in.read(buf)) != -1)
                 out.write(buf, 0, read);
+
+            if (newsExists)
+                out.write(FOOTER.getBytes());
             
         } catch (IOException ioe) {
+            System.err.println("news error " + ioe);
         } catch (Resources.NotFoundException nfe) {
         } finally {
             if (in != null) try { in.close(); } catch (IOException ioe) {}
@@ -45,4 +87,44 @@ public class NewsActivity extends I2PActivityBase {
         } catch (UnsupportedEncodingException uee) {
         }
     }
+
+    @Override
+    public boolean onKeyDown(int keyCode, KeyEvent event) {
+        WebView wv = (WebView) findViewById(R.id.news_webview);
+        if ((keyCode == KeyEvent.KEYCODE_BACK) && wv.canGoBack()) {
+            wv.goBack();
+            return true;
+        }
+        return super.onKeyDown(keyCode, event);
+    }
+
+    private static class NewsWebViewClient extends WebViewClient {
+        @Override
+        public boolean shouldOverrideUrlLoading(WebView view, String url) {
+            System.err.println("Should override? " + url);
+            try {
+                URI uri = new URI(url);
+                String s = uri.getScheme();
+                if (s == null)
+                    return false;
+                s = s.toLowerCase();
+                if (!(s.equals("http") || s.equals("https")))
+                    return false;
+                String h = uri.getHost();
+                if (h == null)
+                    return false;
+                h = h.toLowerCase();
+                if (h.endsWith(".i2p")) {
+                    // if (s.equals("https")
+                    //    return false;
+                    view.loadData(ERROR_EEPSITE, "text/html", "UTF-8");
+                } else {
+                    view.loadUrl(url);
+                }
+                return true;
+            } catch (URISyntaxException use) {
+                return false;
+            }
+        }
+    }
 }
diff --git a/src/net/i2p/android/router/receiver/I2PReceiver.java b/src/net/i2p/android/router/receiver/I2PReceiver.java
index 274b36419ba245aabec4a962f1c3870ade7db5ce..f9f5bd1f33fcd33e1c499020573e04241b3d67b8 100644
--- a/src/net/i2p/android/router/receiver/I2PReceiver.java
+++ b/src/net/i2p/android/router/receiver/I2PReceiver.java
@@ -17,6 +17,7 @@ public class I2PReceiver extends BroadcastReceiver {
     private final Context _context;
     private boolean _isBound;
     private RouterService _routerService;
+    private ServiceConnection _connection;
 
     /**
      *  Registers itself
@@ -28,8 +29,6 @@ public class I2PReceiver extends BroadcastReceiver {
         IntentFilter intents = new IntentFilter();
         intents.addAction(Intent.ACTION_TIME_CHANGED);
         intents.addAction(Intent.ACTION_TIME_TICK);  // once per minute, for testing
-        intents.addAction(Intent.ACTION_SCREEN_OFF);
-        intents.addAction(Intent.ACTION_SCREEN_ON);
         intents.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
         context.registerReceiver(this, intents);
         boolean success = bindRouter();
@@ -90,11 +89,17 @@ public class I2PReceiver extends BroadcastReceiver {
         Intent intent = new Intent();
         intent.setClassName(_context, "net.i2p.android.router.service.RouterService");
         System.err.println(this + " calling bindService");
-        boolean success = _context.bindService(intent, new RouterConnection(), Context.BIND_AUTO_CREATE);
+        _connection = new RouterConnection();
+        boolean success = _context.bindService(intent, _connection, Context.BIND_AUTO_CREATE);
         System.err.println(this + " got from bindService: " + success);
         return success;
     }
 
+    public void unbindRouter() {
+        if (_connection != null)
+            _context.unbindService(_connection);
+    }
+
     private class RouterConnection implements ServiceConnection {
 
         public void onServiceConnected(ComponentName name, IBinder service) {
diff --git a/src/net/i2p/android/router/service/Init.java b/src/net/i2p/android/router/service/Init.java
index baaa88841580469a4a5bf3723d875c20c3f49e9d..7eb98ef5b464e61281359f072318909dae937c7a 100644
--- a/src/net/i2p/android/router/service/Init.java
+++ b/src/net/i2p/android/router/service/Init.java
@@ -73,11 +73,14 @@ class Init {
     }
 
     void initialize() {
-        mergeResourceToFile(R.raw.router_config, "router.config");
-        mergeResourceToFile(R.raw.logger_config, "logger.config");
-        mergeResourceToFile(R.raw.i2ptunnel_config, "i2ptunnel.config");
+        Properties props = new Properties();
+        props.setProperty("i2p.dir.temp", myDir + "/tmp");
+        props.setProperty("i2p.dir.pid", myDir + "/tmp");
+        mergeResourceToFile(R.raw.router_config, "router.config", props);
+        mergeResourceToFile(R.raw.logger_config, "logger.config", null);
+        mergeResourceToFile(R.raw.i2ptunnel_config, "i2ptunnel.config", null);
         // FIXME this is a memory hog to merge this way
-        mergeResourceToFile(R.raw.hosts_txt, "hosts.txt");
+        mergeResourceToFile(R.raw.hosts_txt, "hosts.txt", null);
         copyResourceToFile(R.raw.blocklist_txt, "blocklist.txt");
 
         // Set up the locations so Router and WorkingDir can find them
@@ -115,8 +118,9 @@ class Init {
      *  and write back
      *  For now, do it backwards so we can override with new apks.
      *  When we have user configurable stuff, switch it back.
+     *  @param props local overrides or null
      */
-    private void mergeResourceToFile(int resID, String f) {
+    private void mergeResourceToFile(int resID, String f, Properties overrides) {
         InputStream in = null;
         InputStream fin = null;
 
@@ -138,6 +142,8 @@ class Init {
             // override user settings
             DataHelper.loadProps(props,  in);
 
+            if (overrides != null)
+                props.putAll(overrides);
             DataHelper.storeProps(props, ctx.getFileStreamPath(f));
         } catch (IOException ioe) {
         } catch (Resources.NotFoundException nfe) {
diff --git a/src/net/i2p/android/router/service/LoadClientsJob.java b/src/net/i2p/android/router/service/LoadClientsJob.java
index 21180f4d173450af202490720195b830fb91f9ed..e74bf744e787e7ab4879641518f75a0dd28262fe 100644
--- a/src/net/i2p/android/router/service/LoadClientsJob.java
+++ b/src/net/i2p/android/router/service/LoadClientsJob.java
@@ -1,10 +1,11 @@
 package net.i2p.android.router.service;
 
+import net.i2p.android.apps.NewsFetcher;
 import net.i2p.i2ptunnel.TunnelControllerGroup;
 import net.i2p.router.Job;
 import net.i2p.router.JobImpl;
 import net.i2p.router.RouterContext;
-import net.i2p.util.I2PThread;
+import net.i2p.util.I2PAppThread;
 
 /**
  * Load the clients we want.
@@ -27,6 +28,8 @@ import net.i2p.util.I2PThread;
  */
 class LoadClientsJob extends JobImpl {
     
+    private Thread _fetcherThread;
+
     /** this is the delay to load the clients. There are additional delays e.g. in i2ptunnel.config */
     private static final long LOAD_DELAY = 10*1000;
 
@@ -41,10 +44,15 @@ class LoadClientsJob extends JobImpl {
     public void runJob() {
         Job j = new RunI2PTunnel(getContext());
         getContext().jobQueue().addJob(j);
+
+        NewsFetcher fetcher = NewsFetcher.getInstance(getContext());
+        _fetcherThread = new I2PAppThread(fetcher, "NewsFetcher", true);
+        _fetcherThread.start();
+
         // add other clients here
     }
 
-    private static class RunI2PTunnel extends JobImpl {
+    private class RunI2PTunnel extends JobImpl {
 
         public RunI2PTunnel(RouterContext ctx) {
             super(ctx);
@@ -61,9 +69,11 @@ class LoadClientsJob extends JobImpl {
         }
     }
 
-    private static class I2PTunnelShutdownHook implements Runnable {
+    private class I2PTunnelShutdownHook implements Runnable {
         public void run() {
             System.err.println("i2ptunnel shutdown hook");
+            if (_fetcherThread != null)
+                _fetcherThread.interrupt();
             TunnelControllerGroup.getInstance().unloadControllers();
         }
     }
diff --git a/src/net/i2p/android/router/service/RouterService.java b/src/net/i2p/android/router/service/RouterService.java
index ed998663953e3ba6a750b35fed7f029e97ee5125..a9f27ff72d6999aaa5375fd9e556c2251dc59231 100644
--- a/src/net/i2p/android/router/service/RouterService.java
+++ b/src/net/i2p/android/router/service/RouterService.java
@@ -201,6 +201,9 @@ public class RouterService extends Service {
 
     // ******** following methods may be accessed from Activities and Receivers ************
 
+    /**
+     *  @returns null if router is not running
+     */
     public RouterContext getRouterContext() {
         RouterContext rv = _context;
         if (rv == null)
@@ -215,10 +218,35 @@ public class RouterService extends Service {
         return rv;
     }
 
+    /**
+     *  Stop and don't restart
+     */
     public void manualStop() {
+        synchronized (_stateLock) {
+            if (_state == State.WAITING || _state == State.STARTING)
+                _starterThread.interrupt();
+            if (_state == State.STARTING || _state == State.RUNNING) {
+                _statusBar.update("I2P is stopping");
+                Thread stopperThread = new Thread(new Stopper(State.MANUAL_STOPPING, State.MANUAL_STOPPED));
+                stopperThread.start();
+            }
+        }
     }
 
+    /**
+     *  Stop and then spin waiting for a network connection, then restart
+     */
     public void networkStop() {
+        synchronized (_stateLock) {
+            if (_state == State.WAITING || _state == State.STARTING)
+                _starterThread.interrupt();
+            if (_state == State.STARTING || _state == State.RUNNING) {
+                _statusBar.update("I2P is stopping");
+                // don't change state, let the shutdown hook do it
+                Thread stopperThread = new Thread(new Stopper(State.NETWORK_STOPPING, State.NETWORK_STOPPING));
+                stopperThread.start();
+            }
+        }
     }
 
     public void restart() {
@@ -231,29 +259,39 @@ public class RouterService extends Service {
         System.err.println("onDestroy called" +
                            "Current state is: " + _state);
 
-        BroadcastReceiver rcvr = _receiver;
+        _statusBar.off(this);
+
+        I2PReceiver rcvr = _receiver;
         if (rcvr != null) {
             synchronized(rcvr) {
                 unregisterReceiver(rcvr);
+                rcvr.unbindRouter();
                 _receiver = null;
             }
         }
         synchronized (_stateLock) {
-            if (_state == State.STARTING)
+            if (_state == State.WAITING || _state == State.STARTING)
                 _starterThread.interrupt();
             if (_state == State.STARTING || _state == State.RUNNING) {
-                _state = State.STOPPING;
               // should this be in a thread?
                 _statusBar.update("I2P is stopping");
-                Thread stopperThread = new Thread(new Stopper());
+                Thread stopperThread = new Thread(new Stopper(State.STOPPING, State.STOPPED));
                 stopperThread.start();
-            } else if (_state != State.STOPPING) {
-                _statusBar.off(this);
             }
         }
     }
 
     private class Stopper implements Runnable {
+        private final State nextState;
+        private final State stopState;
+
+        /** call holding statelock */
+        public Stopper(State next, State stop) {
+            nextState = next;
+            stopState = stop;
+            _state = next;
+        }
+
         public void run() {
             System.err.println(MARKER + this + " stopper thread" +
                                "Current state is: " + _state);
@@ -262,7 +300,8 @@ public class RouterService extends Service {
             _statusBar.off(RouterService.this);
             System.err.println("shutdown complete");
             synchronized (_stateLock) {
-                _state = State.STOPPED;
+                if (_state == nextState)
+                    _state = stopState;
             }
         }
     }
@@ -272,15 +311,26 @@ public class RouterService extends Service {
             System.err.println(this + " shutdown hook" +
                                "Current state is: " + _state);
             _statusBar.off(RouterService.this);
-            BroadcastReceiver rcvr = _receiver;
+            I2PReceiver rcvr = _receiver;
             if (rcvr != null) {
                 synchronized(rcvr) {
                     unregisterReceiver(rcvr);
+                    rcvr.unbindRouter();
                     _receiver = null;
                 }
             }
             synchronized (_stateLock) {
-                if (_state == State.STARTING || _state == State.RUNNING) {
+                if (_state == State.WAITING || _state == State.STARTING)
+                    _starterThread.interrupt();
+                if (_state == State.MANUAL_STOPPING) {
+                    _state = State.MANUAL_STOPPED;
+                } else if (_state == State.NETWORK_STOPPING) {
+                    // start waiter thread
+                    _state = State.WAITING;
+                    _starterThread = new Thread(new Waiter());
+                    _starterThread.start();
+                } else if (_state == State.STARTING || _state == State.RUNNING ||
+                           _state == State.STOPPING) {
                     _state = State.STOPPED;
                     if (_statusThread != null)
                         _statusThread.interrupt();