From a9a3fbd5da83ad248121e3c05cdfe9bfa93d9420 Mon Sep 17 00:00:00 2001
From: zzz <zzz@mail.i2p>
Date: Sat, 11 Jun 2011 20:08:53 +0000
Subject: [PATCH] - Start clients directly (hard coded) instead of using
 clients.config   and LoadClientAppsJob, due to ClassLoader issues and the
 need to   register shutdown hooks anyway. - Status bar tweaks - Fix NPE when
 stopping - Instantiate broadcast receiver - apk config has preference over
 installed config for now

---
 res/raw/clients_config                        |  5 --
 .../android/router/receiver/I2PReceiver.java  | 38 +++++++++-
 src/net/i2p/android/router/service/Init.java  | 13 ++--
 .../router/service/LoadClientsJob.java        | 70 +++++++++++++++++++
 .../android/router/service/RouterService.java | 48 +++++++++----
 .../i2p/android/router/service/StatusBar.java |  4 ++
 6 files changed, 151 insertions(+), 27 deletions(-)
 delete mode 100644 res/raw/clients_config
 create mode 100644 src/net/i2p/android/router/service/LoadClientsJob.java

diff --git a/res/raw/clients_config b/res/raw/clients_config
deleted file mode 100644
index 0cf3bc988..000000000
--- a/res/raw/clients_config
+++ /dev/null
@@ -1,5 +0,0 @@
-# poke the i2ptunnels defined in i2ptunnel.config
-clientApp.0.main=net.i2p.i2ptunnel.TunnelControllerGroup
-clientApp.0.name=Application tunnels
-clientApp.0.args=i2ptunnel.config
-clientApp.0.startOnLoad=true
diff --git a/src/net/i2p/android/router/receiver/I2PReceiver.java b/src/net/i2p/android/router/receiver/I2PReceiver.java
index b2b8fcb8e..1116cf775 100644
--- a/src/net/i2p/android/router/receiver/I2PReceiver.java
+++ b/src/net/i2p/android/router/receiver/I2PReceiver.java
@@ -12,9 +12,13 @@ import net.i2p.android.router.R;
 public class I2PReceiver extends BroadcastReceiver {
     private final Context _context;
 
+    /**
+     *  Registers itself
+     */
     public I2PReceiver(Context context) {
         super();
         _context = context;
+        getInfo();
         IntentFilter intents = new IntentFilter();
         intents.addAction(Intent.ACTION_TIME_CHANGED);
         intents.addAction(Intent.ACTION_TIME_TICK);  // once per minute, for testing
@@ -26,7 +30,7 @@ public class I2PReceiver extends BroadcastReceiver {
 
     public void onReceive(Context context, Intent intent) {
         String action = intent.getAction();
-        System.out.println("Got broadcast: " + action);
+        System.err.println("Got broadcast: " + action);
 
         if (action.equals(ConnectivityManager.CONNECTIVITY_ACTION)) {
             boolean failover = intent.getBooleanExtra(ConnectivityManager.EXTRA_IS_FAILOVER, false);
@@ -34,9 +38,37 @@ public class I2PReceiver extends BroadcastReceiver {
             NetworkInfo info = (NetworkInfo) intent.getParcelableExtra(ConnectivityManager.EXTRA_NETWORK_INFO);
             NetworkInfo other = (NetworkInfo) intent.getParcelableExtra(ConnectivityManager.EXTRA_OTHER_NETWORK_INFO);
 
-            System.out.println("No conn? " + noConn + " failover? " + failover + 
+            System.err.println("No conn? " + noConn + " failover? " + failover + 
                                " info: " + info + " other: " + other);
-            //ConnectivityManager cm = (ConnectivityManager) _context.getSystemService(Context.CONNECTIVITY_SERVICE);
+            printInfo(info);
+            printInfo(other);
+            getInfo();
         }
     }
+
+    private NetworkInfo getInfo() {
+        ConnectivityManager cm = (ConnectivityManager) _context.getSystemService(Context.CONNECTIVITY_SERVICE);
+        NetworkInfo current = cm.getActiveNetworkInfo();
+        System.err.println("Current network info:");
+        printInfo(current);
+        return current;
+    }
+
+    private static void printInfo(NetworkInfo ni) {
+        if (ni == null) {
+            System.err.println("Network info is null");
+            return;
+        }
+        System.err.println(
+             "state: " + ni.getState() +
+             " detail: " + ni.getDetailedState() +
+             " extrainfo: " + ni.getExtraInfo() +
+             " reason: " + ni.getReason() +
+             " typename: " + ni.getTypeName() +
+             " available: " + ni.isAvailable() +
+             " connected: " + ni.isConnected() +
+             " conorcon: " + ni.isConnectedOrConnecting() +
+             " failover: " + ni.isFailover());
+
+    }
 }
diff --git a/src/net/i2p/android/router/service/Init.java b/src/net/i2p/android/router/service/Init.java
index 772192bcb..baaa88841 100644
--- a/src/net/i2p/android/router/service/Init.java
+++ b/src/net/i2p/android/router/service/Init.java
@@ -75,7 +75,6 @@ class Init {
     void initialize() {
         mergeResourceToFile(R.raw.router_config, "router.config");
         mergeResourceToFile(R.raw.logger_config, "logger.config");
-        mergeResourceToFile(R.raw.clients_config, "clients.config");
         mergeResourceToFile(R.raw.i2ptunnel_config, "i2ptunnel.config");
         // FIXME this is a memory hog to merge this way
         mergeResourceToFile(R.raw.hosts_txt, "hosts.txt");
@@ -114,6 +113,8 @@ class Init {
      *  Load defaults from resource,
      *  then add props from file,
      *  and write back
+     *  For now, do it backwards so we can override with new apks.
+     *  When we have user configurable stuff, switch it back.
      */
     private void mergeResourceToFile(int resID, String f) {
         InputStream in = null;
@@ -121,9 +122,10 @@ class Init {
 
         byte buf[] = new byte[4096];
         try {
-            Properties props = new OrderedProperties();
             in = ctx.getResources().openRawResource(resID);
-            DataHelper.loadProps(props,  in);
+            Properties props = new OrderedProperties();
+            // keep user settings
+            //DataHelper.loadProps(props,  in);
             
             try {
                 fin = ctx.openFileInput(f);
@@ -131,10 +133,11 @@ class Init {
                 System.err.println("Merging resource into file " + f);
             } catch (IOException ioe) {
                 System.err.println("Creating file " + f + " from resource");
-            } finally {
-                if (fin != null) try { fin.close(); } catch (IOException ioe) {}
             }
 
+            // override user settings
+            DataHelper.loadProps(props,  in);
+
             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
new file mode 100644
index 000000000..21180f4d1
--- /dev/null
+++ b/src/net/i2p/android/router/service/LoadClientsJob.java
@@ -0,0 +1,70 @@
+package net.i2p.android.router.service;
+
+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;
+
+/**
+ * Load the clients we want.
+ *
+ * We can't use LoadClientAppsJob (reading in clients.config) directly
+ * because Class.forName() needs a PathClassLoader argument -
+ * http://doandroids.com/blogs/2010/6/10/android-classloader-dynamic-loading-of/
+ * ClassLoader cl = new PathClassLoader(_apkPath, ClassLoader.getSystemClassLoader());
+ *
+ * We can't extend LoadClientAppsJob to specify a class loader,
+ * even if we use it only for Class.forName() and not for
+ * setContextClassLoader(), because I2PTunnel still
+ * can't find the existing static RouterContext due to the new class loader.
+ *
+ * Also, if we load them that way, we can't register a shutdown hook.
+ *
+ * So fire off the ones we want here, without a clients.config file and
+ * without using Class.forName().
+ *
+ */
+class LoadClientsJob extends JobImpl {
+    
+    /** 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;
+
+
+    public LoadClientsJob(RouterContext ctx) {
+        super(ctx);
+        getTiming().setStartAfter(getContext().clock().now() + LOAD_DELAY);
+    }
+
+    public String getName() { return "Start Clients"; };
+
+    public void runJob() {
+        Job j = new RunI2PTunnel(getContext());
+        getContext().jobQueue().addJob(j);
+        // add other clients here
+    }
+
+    private static class RunI2PTunnel extends JobImpl {
+
+        public RunI2PTunnel(RouterContext ctx) {
+            super(ctx);
+        }
+
+        public String getName() { return "Start I2P Tunnel"; };
+
+        public void runJob() {
+            System.err.println("Starting i2ptunnel");
+            TunnelControllerGroup.main(null);
+            System.err.println("i2ptunnel started");
+            getContext().addShutdownTask(new I2PTunnelShutdownHook());
+
+        }
+    }
+
+    private static class I2PTunnelShutdownHook implements Runnable {
+        public void run() {
+            System.err.println("i2ptunnel shutdown hook");
+            TunnelControllerGroup.getInstance().unloadControllers();
+        }
+    }
+}
diff --git a/src/net/i2p/android/router/service/RouterService.java b/src/net/i2p/android/router/service/RouterService.java
index 99f3cdb9d..c47e53762 100644
--- a/src/net/i2p/android/router/service/RouterService.java
+++ b/src/net/i2p/android/router/service/RouterService.java
@@ -1,18 +1,20 @@
 package net.i2p.android.router.service;
 
 import android.app.Service;
+import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
 import android.os.Bundle;
 import android.os.IBinder;
 
-import dalvik.system.PathClassLoader;
-
+import java.io.File;
 import java.text.DecimalFormat;
 import java.util.List;
 
 import net.i2p.android.router.R;
+import net.i2p.android.router.receiver.I2PReceiver;
 import net.i2p.data.DataHelper;
+import net.i2p.router.Job;
 import net.i2p.router.Router;
 import net.i2p.router.RouterContext;
 import net.i2p.router.RouterLaunch;
@@ -26,11 +28,12 @@ public class RouterService extends Service {
 
     private RouterContext _context;
     private String _myDir;
-    private String _apkPath;
+    //private String _apkPath;
     private State _state = State.INIT;
     private Thread _starterThread;
     private Thread _statusThread;
     private StatusBar _statusBar;
+    private BroadcastReceiver _receiver;
     private final Object _stateLock = new Object();
 
     private static final String MARKER = "**************************************  ";
@@ -44,7 +47,7 @@ public class RouterService extends Service {
         Init init = new Init(this);
         init.debugStuff();
         init.initialize();
-        _apkPath = init.getAPKPath();
+        //_apkPath = init.getAPKPath();
         _statusBar = new StatusBar(this);
     }
 
@@ -59,11 +62,8 @@ public class RouterService extends Service {
             _state = State.STARTING;
 
             _starterThread = new Thread(new Starter());
-            // this is required for Class.forName() to work in LoadClientAppsJob
-            // http://doandroids.com/blogs/2010/6/10/android-classloader-dynamic-loading-of/
-            ClassLoader cl = new PathClassLoader(_apkPath, ClassLoader.getSystemClassLoader());
-            _starterThread.setContextClassLoader(cl);
             _starterThread.start();
+            _receiver = new I2PReceiver(this);
         }
         return START_STICKY;
     }
@@ -86,6 +86,8 @@ public class RouterService extends Service {
                 _statusBar.update("I2P is running");
                 _context = (RouterContext)contexts.get(0);
                 _context.router().setKillVMOnEnd(false);
+                Job loadJob = new LoadClientsJob(_context);
+                _context.jobQueue().addJob(loadJob);
                 _statusThread = new Thread(new StatusThread());
                 _statusThread.start();
                 _context.addShutdownTask(new ShutdownHook());
@@ -130,17 +132,19 @@ public class RouterService extends Service {
                     fmt = new DecimalFormat("#0.00");
 
                 String status =
+                       "I2P " +
                        " Pr " + active + '/' + known +
                        " Ex " + inEx + '/' + outEx +
-                       " Cl " + inCl + '/' + outCl +
+                       " Cl " + inCl + '/' + outCl;
                        //" Pt " + part +
-                       " BW " + fmt.format(inBW) + '/' + fmt.format(outBW) + "K" +
+
+                String details =
+                       "BW " + fmt.format(inBW) + '/' + fmt.format(outBW) + "K" +
                        " Lg " + jobLag +
                        " Dy " + msgDelay +
                        " Up " + uptime;
 
-                System.out.println(status);
-                _statusBar.update(status);
+                _statusBar.update(status, details);
             }
             _statusBar.update("Status thread died");
             System.err.println(MARKER + this + " status thread finished" +
@@ -160,6 +164,14 @@ public class RouterService extends Service {
     public void onDestroy() {
         System.err.println("onDestroy called" +
                            "Current state is: " + _state);
+
+        BroadcastReceiver rcvr = _receiver;
+        if (rcvr != null) {
+            synchronized(rcvr) {
+                unregisterReceiver(rcvr);
+                _receiver = null;
+            }
+        }
         synchronized (_stateLock) {
             if (_state == State.STARTING)
                 _starterThread.interrupt();
@@ -179,7 +191,8 @@ public class RouterService extends Service {
         public void run() {
             System.err.println(MARKER + this + " stopper thread" +
                                "Current state is: " + _state);
-            _context.router().shutdown(Router.EXIT_HARD);
+            if (_context != null)
+                _context.router().shutdown(Router.EXIT_HARD);
             _statusBar.off(RouterService.this);
             System.err.println("shutdown complete");
             synchronized (_stateLock) {
@@ -192,12 +205,19 @@ public class RouterService extends Service {
         public void run() {
             System.err.println(this + " shutdown hook" +
                                "Current state is: " + _state);
+            _statusBar.off(RouterService.this);
+            BroadcastReceiver rcvr = _receiver;
+            if (rcvr != null) {
+                synchronized(rcvr) {
+                    unregisterReceiver(rcvr);
+                    _receiver = null;
+                }
+            }
             synchronized (_stateLock) {
                 if (_state == State.STARTING || _state == State.RUNNING) {
                     _state = State.STOPPED;
                     if (_statusThread != null)
                         _statusThread.interrupt();
-                    _statusBar.off(RouterService.this);
                     stopSelf();
                 }
             }
diff --git a/src/net/i2p/android/router/service/StatusBar.java b/src/net/i2p/android/router/service/StatusBar.java
index 08b473fd6..151f7b404 100644
--- a/src/net/i2p/android/router/service/StatusBar.java
+++ b/src/net/i2p/android/router/service/StatusBar.java
@@ -34,6 +34,10 @@ public class StatusBar {
 
     public void update(String details) {
         String title = "I2P Status";
+        update(title, details);
+    }
+
+    public void update(String title, String details) {
         PendingIntent pi = PendingIntent.getActivity(ctx, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
         notif.setLatestEventInfo(ctx, title, details, pi);
         mgr.notify(ID, notif);
-- 
GitLab