From ea4a1f5f9ffe862401b09a3f3de8a9a0701c01cd Mon Sep 17 00:00:00 2001 From: str4d Date: Wed, 4 Jun 2014 10:28:29 +0000 Subject: [PATCH] I2P Android integration --- res/values/strings.xml | 2 + src/i2p/bote/android/EmailListActivity.java | 115 +++++++++++++++++- src/i2p/bote/android/service/BoteService.java | 18 ++- src/i2p/bote/android/service/Init.java | 74 +++++++++++ .../android/router/service/IRouterState.aidl | 33 +++++ .../router/service/IRouterStateCallback.aidl | 13 ++ 6 files changed, 246 insertions(+), 9 deletions(-) create mode 100644 src/i2p/bote/android/service/Init.java create mode 100644 src/net/i2p/android/router/service/IRouterState.aidl create mode 100644 src/net/i2p/android/router/service/IRouterStateCallback.aidl 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); +}