package net.i2p.android.ui;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.ComponentName;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
import net.i2p.android.lib.helper.R;
import net.i2p.android.router.service.IRouterState;
import net.i2p.android.router.service.State;
/**
* @author str4d
* @since 0.2
*/
public class I2PAndroidHelper {
public static final String URI_I2P_ANDROID = "net.i2p.android";
public static final String URI_I2P_ANDROID_DONATE = "net.i2p.android.donate";
public static final String URI_I2P_ANDROID_LEGACY = "net.i2p.android.router";
public static final String URI_I2P_ANDROID_DEBUG = "net.i2p.android.debug";
public static final int REQUEST_START_I2P = 9857;
private static final String ROUTER_SERVICE_CLASS = "net.i2p.android.router.service.RouterService";
private static final String LOG_TAG = "I2PHelperLib";
public interface Callback {
void onI2PAndroidBound();
}
private final Context mContext;
private final boolean mUseDebug;
private boolean mTriedBindState;
private IRouterState mStateService;
private Callback mCallback;
public I2PAndroidHelper(Context context) {
mContext = context;
mUseDebug = false;
}
/**
* @param useDebug Enable usage against debug builds of I2P Android.
*/
public I2PAndroidHelper(Context context, boolean useDebug) {
mContext = context;
mUseDebug = useDebug;
}
/**
* Try to bind to I2P Android. Call this method from
* {@link android.app.Activity#onStart()}.
*
* @param callback will be called once the helper has bound to the I2P app.
* @since 0.7
*/
public void bind(Callback callback) {
mCallback = callback;
bind();
}
/**
* Try to bind to I2P Android. Call this method from
* {@link android.app.Activity#onStart()}.
* <p/>
* If you need to be notified as soon as the helper has been bound to the
* I2P app, use {@link I2PAndroidHelper#bind(Callback)} instead.
*/
public void bind() {
Log.i(LOG_TAG, "Binding to I2P Android");
Intent i2pIntent = getI2PAndroidIntent();
if (i2pIntent != null) {
Log.i(LOG_TAG, i2pIntent.toString());
try {
mTriedBindState = mContext.bindService(
i2pIntent, mStateConnection, Context.BIND_AUTO_CREATE);
if (!mTriedBindState)
Log.w(LOG_TAG, "Could not bind: bindService failed");
} catch (SecurityException e) {
// Old version of I2P Android (pre-0.9.13), cannot use
mStateService = null;
mTriedBindState = false;
Log.w(LOG_TAG, "Could not bind: I2P Android version is too old");
}
} else
Log.w(LOG_TAG, "Could not bind: I2P Android not installed");
}
/**
* Try to bind to I2P Android, using the provided ServiceConnection and
* flags. Call this method from
* {@link android.app.Service#onStartCommand(android.content.Intent, int, int)}.
* The ServiceConnection will be provided with an {@link android.os.IBinder}
* that can be converted to an
* {@link net.i2p.android.router.service.IRouterState} with
* <code>IRouterState.Stub.asInterface(IBinder)</code>.
*/
public boolean bind(ServiceConnection serviceConnection, int flags) {
Log.i(LOG_TAG, "Binding to I2P Android with provided ServiceConnection");
Intent i2pIntent = getI2PAndroidIntent();
if (i2pIntent != null) {
Log.i(LOG_TAG, i2pIntent.toString());
try {
boolean rv = mContext.bindService(
i2pIntent, serviceConnection, flags);
if (!rv)
Log.w(LOG_TAG, "Could not bind: bindService failed");
return rv;
} catch (SecurityException e) {
// Old version of I2P Android (pre-0.9.13), cannot use
Log.w(LOG_TAG, "Could not bind: I2P Android version is too old");
return false;
}
} else {
Log.w(LOG_TAG, "Could not bind: I2P Android not installed");
return false;
}
}
private Intent getI2PAndroidIntent() {
Intent intent = new Intent("net.i2p.android.router.service.IRouterState");
if (isAppInstalled(URI_I2P_ANDROID))
intent.setClassName(URI_I2P_ANDROID, ROUTER_SERVICE_CLASS);
else if (isAppInstalled(URI_I2P_ANDROID_DONATE))
intent.setClassName(URI_I2P_ANDROID_DONATE, ROUTER_SERVICE_CLASS);
else if (isAppInstalled(URI_I2P_ANDROID_LEGACY))
intent.setClassName(URI_I2P_ANDROID_LEGACY, ROUTER_SERVICE_CLASS);
else
intent = null;
if (mUseDebug && isAppInstalled(URI_I2P_ANDROID_DEBUG)) {
Log.w(LOG_TAG, "Using debug build of I2P Android");
intent.setClassName(URI_I2P_ANDROID_DEBUG, ROUTER_SERVICE_CLASS);
}
return intent;
}
/**
* Unbind from I2P Android. Call this method from
* {@link android.app.Activity#onStop()}.
*/
public void unbind() {
if (mTriedBindState)
mContext.unbindService(mStateConnection);
mTriedBindState = false;
mCallback = null;
}
private final ServiceConnection mStateConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className,
IBinder service) {
mStateService = IRouterState.Stub.asInterface(service);
Log.i(LOG_TAG, "Bound to I2P Android");
if (mCallback != null)
mCallback.onI2PAndroidBound();
}
public void onServiceDisconnected(ComponentName className) {
// This is called when the connection with the service has been
// unexpectedly disconnected -- that is, its process crashed.
Log.w(LOG_TAG, "I2P Android disconnected unexpectedly");
mStateService = null;
}
};
/**
* Check if I2P Android is installed.
*
* @return true if I2P Android is installed, false otherwise.
*/
public boolean isI2PAndroidInstalled() {
return (mUseDebug && isAppInstalled(URI_I2P_ANDROID_DEBUG)) ||
isAppInstalled(URI_I2P_ANDROID) ||
isAppInstalled(URI_I2P_ANDROID_DONATE) ||
isAppInstalled(URI_I2P_ANDROID_LEGACY);
}
private boolean isAppInstalled(String uri) {
PackageManager pm = mContext.getPackageManager();
boolean installed;
try {
pm.getPackageInfo(uri, PackageManager.GET_ACTIVITIES);
installed = true;
} catch (PackageManager.NameNotFoundException e) {
installed = false;
}
return installed;
}
/**
* Show dialog - install I2P Android from market or F-Droid.
*
* @param activity the Activity this method has been called from.
*/
public void promptToInstall(final Activity activity) {
AlertDialog.Builder builder = new AlertDialog.Builder(activity);
builder.setTitle(R.string.install_i2p_android)
.setMessage(R.string.you_must_have_i2p_android)
.setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialogInterface, int i) {
String uriMarket = activity.getString(R.string.market_i2p_android);
Uri uri = Uri.parse(uriMarket);
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
activity.startActivity(intent);
}
})
.setNegativeButton(R.string.no, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialogInterface, int i) {
}
});
builder.show();
}
/**
* Check if I2P Android is running. If {@link I2PAndroidHelper#bind()}
* has not been called previously, this will always return false.
* <p/>
* Do not call this from {@link Activity#onResume()}. The Android lifecycle
* calls that method before binding the helper to the I2P app, so this will
* always return false. Use {@link I2PAndroidHelper#bind(Callback)} instead
* and call this method inside the callback.
*
* @return true if I2P Android is running, false otherwise.
*/
public boolean isI2PAndroidRunning() {
if (mStateService == null)
return false;
try {
return mStateService.isStarted();
} catch (RemoteException e) {
Log.w(LOG_TAG, "Failed to communicate with I2P Android", e);
return false;
}
}
/**
* Show dialog - request that I2P Android be started.
*
* @param activity the Activity this method has been called from.
*/
public void requestI2PAndroidStart(final Activity activity) {
AlertDialog.Builder builder = new AlertDialog.Builder(activity);
builder.setTitle(R.string.start_i2p_android)
.setMessage(R.string.would_you_like_to_start_i2p_android)
.setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
Intent i = new Intent("net.i2p.android.router.START_I2P");
activity.startActivityForResult(i, REQUEST_START_I2P);
}
})
.setNegativeButton(R.string.no, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
}
});
builder.show();
}
/**
* Check if tunnels are active. This is the best indicator that the default
* tunnels (such as the HTTP proxy tunnel at localhost:4444) are ready to
* be used. If {@link I2PAndroidHelper#bind()} has not been called
* previously, this will always return false.
* <p/>
* Do not call this from {@link Activity#onResume()}. The Android lifecycle
* calls that method before binding the helper to the I2P app, so this will
* always return false. Use {@link I2PAndroidHelper#bind(Callback)} instead
* and call this method inside the callback.
*
* @return true if tunnels are active, false otherwise.
* @since 0.7
*/
public boolean areTunnelsActive() {
if (mStateService == null)
return false;
try {
return mStateService.getState() == State.ACTIVE;
} catch (RemoteException e) {
Log.w(LOG_TAG, "Failed to communicate with I2P Android", e);
return false;
}
}
}