diff --git a/res/values/strings.xml b/res/values/strings.xml
index c95fc55..01095e6 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -8,6 +8,8 @@
Stop Bote
Settings
+ It appears that I2P Android is not running. Would you like to start it?
+
%s selected
Delete
Mark read
diff --git a/src/i2p/bote/android/EmailListActivity.java b/src/i2p/bote/android/EmailListActivity.java
index b28e6a2..cbd4d5e 100644
--- a/src/i2p/bote/android/EmailListActivity.java
+++ b/src/i2p/bote/android/EmailListActivity.java
@@ -1,19 +1,28 @@
package i2p.bote.android;
+import net.i2p.android.router.service.IRouterState;
import i2p.bote.I2PBote;
import i2p.bote.android.addressbook.AddressBookActivity;
import i2p.bote.android.config.SettingsActivity;
import i2p.bote.android.service.BoteService;
+import i2p.bote.android.service.Init;
+import i2p.bote.android.service.Init.RouterChoice;
import i2p.bote.android.util.MoveToDialogFragment;
import i2p.bote.folder.EmailFolder;
import android.os.Bundle;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.preference.PreferenceManager;
+import android.app.Activity;
import android.app.ActivityManager;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.ActivityManager.RunningServiceInfo;
+import android.content.ComponentName;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
+import android.content.ServiceConnection;
import android.content.SharedPreferences;
import android.content.res.Configuration;
import android.graphics.drawable.Drawable;
@@ -46,11 +55,15 @@ public class EmailListActivity extends ActionBarActivity implements
private ListView mFolderList;
private TextView mNetworkStatus;
private ActionBarDrawerToggle mDrawerToggle;
+ RouterChoice mRouterChoice;
+ IRouterState mStateService = null;
private static final String SHARED_PREFS = "i2p.bote";
private static final String PREF_NAV_DRAWER_OPENED = "navDrawerOpened";
private static final String ACTIVE_FOLDER = "activeFolder";
+ private static final int REQUEST_START_I2P = 1;
+
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -232,6 +245,28 @@ public class EmailListActivity extends ActionBarActivity implements
return super.onCreateOptionsMenu(menu);
}
+ @Override
+ protected void onStart() {
+ super.onStart();
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
+ if (prefs.getBoolean("i2pbote.router.auto", true) ||
+ prefs.getString("i2pbote.router.use", "internal").equals("android")) {
+ // Try to bind to I2P Android
+ Intent i2pIntent = new Intent(IRouterState.class.getName());
+ i2pIntent.setClassName("net.i2p.android.router",
+ "net.i2p.android.router.service.RouterService");
+ mTriedBindState = bindService(i2pIntent, mStateConnection, BIND_AUTO_CREATE);
+ }
+ }
+
+ @Override
+ protected void onStop() {
+ super.onStop();
+ if (mTriedBindState)
+ unbindService(mStateConnection);
+ mTriedBindState = false;
+ }
+
private boolean isBoteServiceRunning() {
ActivityManager manager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
for (RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)) {
@@ -251,9 +286,46 @@ public class EmailListActivity extends ActionBarActivity implements
switch (item.getItemId()) {
case R.id.action_start_bote:
- Intent start = new Intent(this, BoteService.class);
- startService(start);
- supportInvalidateOptionsMenu();
+ // Init from settings
+ Init init = new Init(this);
+ mRouterChoice = init.initialize(mStateService);
+
+ if (mRouterChoice == RouterChoice.ANDROID) {
+ try {
+ if (mStateService == null) {
+ // I2P Android not installed
+ // TODO: handle
+ } else if (!mStateService.isStarted()) {
+ // Ask user to start I2P Android
+ DialogFragment df = new DialogFragment() {
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
+ builder.setMessage(R.string.start_i2p_android)
+ .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ dialog.dismiss();
+ Intent i = new Intent("net.i2p.android.router");
+ i.setAction("net.i2p.android.router.START_I2P");
+ startActivityForResult(i, REQUEST_START_I2P);
+ }
+ })
+ .setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ dialog.cancel();
+ }
+ });
+ return builder.create();
+ }
+ };
+ df.show(getSupportFragmentManager(), "starti2p");
+ } else
+ startBote();
+ } catch (RemoteException e) {
+ // TODO log
+ }
+ } else
+ startBote();
return true;
case R.id.action_stop_bote:
@@ -272,6 +344,24 @@ public class EmailListActivity extends ActionBarActivity implements
}
}
+ @Override
+ public void onActivityResult(int requestCode, int resultCode, Intent data) {
+ if (requestCode == REQUEST_START_I2P) {
+ if (resultCode == Activity.RESULT_OK) {
+ startBote();
+ }
+ } else {
+ super.onActivityResult(requestCode, resultCode, data);
+ }
+ }
+
+ private void startBote() {
+ Intent start = new Intent(this, BoteService.class);
+ start.putExtra(BoteService.ROUTER_CHOICE, mRouterChoice);
+ startService(start);
+ supportInvalidateOptionsMenu();
+ }
+
@Override
public void setTitle(CharSequence title) {
mTitle = title;
@@ -329,4 +419,23 @@ public class EmailListActivity extends ActionBarActivity implements
EmailListFragment f = (EmailListFragment) getSupportFragmentManager().findFragmentById(R.id.list_fragment);
f.onFolderSelected(newFolder);
}
+
+
+ //
+ // I2P Android helpers
+ //
+
+ private boolean mTriedBindState;
+ private ServiceConnection mStateConnection = new ServiceConnection() {
+ public void onServiceConnected(ComponentName className,
+ IBinder service) {
+ mStateService = IRouterState.Stub.asInterface(service);
+ }
+
+ public void onServiceDisconnected(ComponentName className) {
+ // This is called when the connection with the service has been
+ // unexpectedly disconnected -- that is, its process crashed.
+ mStateService = null;
+ }
+ };
}
diff --git a/src/i2p/bote/android/service/BoteService.java b/src/i2p/bote/android/service/BoteService.java
index 5d8aa2b..37fe628 100644
--- a/src/i2p/bote/android/service/BoteService.java
+++ b/src/i2p/bote/android/service/BoteService.java
@@ -6,20 +6,21 @@ import net.i2p.router.Router;
import net.i2p.router.RouterContext;
import net.i2p.router.RouterLaunch;
import i2p.bote.I2PBote;
+import i2p.bote.android.service.Init.RouterChoice;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
public class BoteService extends Service {
- boolean mUseInternalRouter;
+ public static final String ROUTER_CHOICE = "router_choice";
+
+ RouterChoice mRouterChoice;
RouterContext mRouterContext;
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
- // Init from settings
- Init init = new Init(this);
- mUseInternalRouter = init.initialize();
- if (mUseInternalRouter)
+ mRouterChoice = (RouterChoice) intent.getSerializableExtra(ROUTER_CHOICE);
+ if (mRouterChoice == RouterChoice.INTERNAL)
new Thread(new RouterStarter()).start();
I2PBote.getInstance().startUp();
return START_STICKY;
@@ -33,10 +34,15 @@ public class BoteService extends Service {
@Override
public void onDestroy() {
I2PBote.getInstance().shutDown();
- if (mUseInternalRouter)
+ if (mRouterChoice == RouterChoice.INTERNAL)
new Thread(new RouterStopper()).start();
}
+
+ //
+ // Internal router helpers
+ //
+
private class RouterStarter implements Runnable {
public void run() {
RouterLaunch.main(null);
diff --git a/src/i2p/bote/android/service/Init.java b/src/i2p/bote/android/service/Init.java
new file mode 100644
index 0000000..de581b3
--- /dev/null
+++ b/src/i2p/bote/android/service/Init.java
@@ -0,0 +1,74 @@
+package i2p.bote.android.service;
+
+import net.i2p.android.router.service.IRouterState;
+import net.i2p.client.I2PClient;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.preference.PreferenceManager;
+
+public class Init {
+ private final Context ctx;
+ private final String myDir;
+
+ public enum RouterChoice {
+ INTERNAL,
+ ANDROID,
+ REMOTE;
+ }
+
+ public Init(Context c) {
+ ctx = c;
+ // This needs to be changed so that we can have an alternative place
+ myDir = c.getFilesDir().getAbsolutePath();
+ }
+
+ /**
+ * Parses settings and prepares the system for starting the Bote service.
+ * @return true if we should use the internal router, false otherwise.
+ */
+ public RouterChoice initialize(IRouterState mStateService) {
+ // Set up the locations so Router and WorkingDir can find them
+ // We do this again here, in the event settings were changed.
+ System.setProperty("i2p.dir.base", myDir);
+ System.setProperty("i2p.dir.config", myDir);
+ System.setProperty("wrapper.logfile", myDir + "/wrapper.log");
+
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(ctx);
+ RouterChoice routerChoice;
+ String i2cpHost, i2cpPort;
+ if (prefs.getBoolean("i2pbote.router.auto", true)) {
+ if (mStateService != null) {
+ routerChoice = RouterChoice.ANDROID;
+ // TODO fetch settings from I2P Android
+ i2cpHost = "127.0.0.1";
+ i2cpPort = "7654";
+ } else {
+ routerChoice = RouterChoice.INTERNAL;
+ i2cpHost = "internal";
+ i2cpPort = "internal";
+ }
+ } else {
+ // Check manual settings
+ String which = prefs.getString("i2pbote.router.use", "internal");
+ if ("internal".equals(which)) {
+ routerChoice = RouterChoice.INTERNAL;
+ i2cpHost = "internal";
+ i2cpPort = "internal";
+ } else if ("android".equals(which)) {
+ routerChoice = RouterChoice.ANDROID;
+ // TODO fetch settings from I2P Android
+ i2cpHost = "127.0.0.1";
+ i2cpPort = "7654";
+ } else { // Remote router
+ routerChoice = RouterChoice.REMOTE;
+ i2cpHost = prefs.getString("i2pbote.i2cp.tcp.host", "127.0.0.1");
+ i2cpPort = prefs.getString("i2pbote.i2cp.tcp.port", "7654");
+ }
+ }
+ // Set the I2CP host/port
+ System.setProperty(I2PClient.PROP_TCP_HOST, i2cpHost);
+ System.setProperty(I2PClient.PROP_TCP_PORT, i2cpPort);
+
+ return routerChoice;
+ }
+}
diff --git a/src/net/i2p/android/router/service/IRouterState.aidl b/src/net/i2p/android/router/service/IRouterState.aidl
new file mode 100644
index 0000000..1a087bc
--- /dev/null
+++ b/src/net/i2p/android/router/service/IRouterState.aidl
@@ -0,0 +1,33 @@
+package net.i2p.android.router.service;
+
+import net.i2p.android.router.service.IRouterStateCallback;
+
+/**
+ * An interface for determining the state of the I2P RouterService.
+ */
+interface IRouterState {
+
+ /**
+ * This allows I2P to inform on state changes.
+ */
+ void registerCallback(IRouterStateCallback cb);
+
+ /**
+ * Remove registered callback interface.
+ */
+ void unregisterCallback(IRouterStateCallback cb);
+
+ /**
+ * Determines whether the RouterService has been started. If it hasn't, no
+ * state changes will ever occur from this RouterService instance, and the
+ * client should unbind and inform the user that the I2P router is not
+ * running (and optionally send a net.i2p.android.router.START_I2P Intent).
+ */
+ boolean isStarted();
+
+ /**
+ * Get the state of the I2P router
+ **/
+ String getState();
+
+}
diff --git a/src/net/i2p/android/router/service/IRouterStateCallback.aidl b/src/net/i2p/android/router/service/IRouterStateCallback.aidl
new file mode 100644
index 0000000..a5ee84c
--- /dev/null
+++ b/src/net/i2p/android/router/service/IRouterStateCallback.aidl
@@ -0,0 +1,13 @@
+package net.i2p.android.router.service;
+
+/**
+ * Callback interface used to send synchronous notifications of the current
+ * RouterService state back to registered clients. Note that this is a
+ * one-way interface so the server does not block waiting for the client.
+ */
+oneway interface IRouterStateCallback {
+ /**
+ * Called when the state of the I2P router changes
+ */
+ void stateChanged(String newState);
+}