I2P Android integration

This commit is contained in:
str4d
2014-06-04 10:28:29 +00:00
parent c0f69852b2
commit ea4a1f5f9f
6 changed files with 246 additions and 9 deletions

View File

@@ -8,6 +8,8 @@
<string name="action_stop_bote">Stop Bote</string>
<string name="action_settings">Settings</string>
<string name="start_i2p_android">It appears that I2P Android is not running. Would you like to start it?</string>
<string name="items_selected">%s selected</string>
<string name="action_delete">Delete</string>
<string name="action_mark_read">Mark read</string>

View File

@@ -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;
}
};
}

View File

@@ -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);

View File

@@ -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;
}
}

View File

@@ -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();
}

View File

@@ -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);
}