diff --git a/android/.gitignore b/android/.gitignore index d9fa5a57..7e166aa6 100644 --- a/android/.gitignore +++ b/android/.gitignore @@ -5,4 +5,8 @@ ant.properties local.properties build.sh bin -log* \ No newline at end of file +log* +.gradle* +build +assets +gradle-app.setting diff --git a/android/build.gradle b/android/build.gradle index a88403fd..46d0d057 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -11,23 +11,24 @@ buildscript { apply plugin: 'com.android.application' android { - compileSdkVersion 25 - buildToolsVersion "25.0.2" - defaultConfig { - applicationId "org.purplei2p.i2pd" - targetSdkVersion 25 - minSdkVersion 14 - versionCode 1 - versionName "2.17.1" - } + compileSdkVersion 25 + buildToolsVersion "25.0.0" + defaultConfig { + applicationId "org.purplei2p.i2pd" + targetSdkVersion 25 + minSdkVersion 14 + versionCode 1 + versionName "2.17.2e" + } sourceSets { main { manifest.srcFile 'AndroidManifest.xml' java.srcDirs = ['src'] - res.srcDirs = ['res'] - jniLibs.srcDirs = ['libs'] - } + res.srcDirs = ['res'] + jniLibs.srcDirs = ['libs'] + assets.srcDirs = ['assets'] } + } signingConfigs { orignal { storeFile file("i2pdapk.jks") @@ -37,11 +38,30 @@ android { } } buildTypes { - release { - minifyEnabled false - signingConfig signingConfigs.orignal - proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-project.txt' - } + release { + minifyEnabled false + signingConfig signingConfigs.orignal + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-project.txt' + } } } +tasks.withType(JavaCompile) { + options.compilerArgs << '-Xlint:unchecked' << '-Xlint:deprecation' + options.deprecation = true +} + +task zipCerts(type:Zip) { + from (files('../contrib/')) + include 'certificates/**/*.crt' + destinationDir file('assets') + archiveName 'certificates.zip' + entryCompression ZipEntryCompression.STORED +} +preBuild.dependsOn zipCerts + +task clean(type: Delete,overwrite: true) { + delete 'build' + delete 'assets' +} + diff --git a/android/jni/DaemonAndroid.cpp b/android/jni/DaemonAndroid.cpp index 75584740..9865ac79 100644 --- a/android/jni/DaemonAndroid.cpp +++ b/android/jni/DaemonAndroid.cpp @@ -126,12 +126,11 @@ namespace android } */ static DaemonAndroidImpl daemon; - static char* argv[1]={strdup("tmp")}; /** * returns error details if failed * returns "ok" if daemon initialized and started okay */ - std::string start(/*int argc, char* argv[]*/) + std::string start(int argc, char* argv[]) { try { @@ -139,7 +138,7 @@ namespace android { //Log.d(TAG"Initialising the daemon..."); - bool daemonInitSuccess = daemon.init(1,argv); + bool daemonInitSuccess = daemon.init(argc,argv); if(!daemonInitSuccess) { //QMessageBox::critical(0, "Error", "Daemon init failed"); diff --git a/android/jni/DaemonAndroid.h b/android/jni/DaemonAndroid.h index 9cc8219b..81031936 100644 --- a/android/jni/DaemonAndroid.h +++ b/android/jni/DaemonAndroid.h @@ -37,7 +37,7 @@ namespace android * returns "ok" if daemon init failed * returns errinfo if daemon initialized and started okay */ - std::string start(); + std::string start(int argc, char* argv[]); // stops the daemon void stop(); diff --git a/android/jni/i2pd_android.cpp b/android/jni/i2pd_android.cpp index 8791c90b..1079a252 100755 --- a/android/jni/i2pd_android.cpp +++ b/android/jni/i2pd_android.cpp @@ -44,8 +44,24 @@ JNIEXPORT jstring JNICALL Java_org_purplei2p_i2pd_I2PD_1JNI_getABICompiledWith } JNIEXPORT jstring JNICALL Java_org_purplei2p_i2pd_I2PD_1JNI_startDaemon - (JNIEnv * env, jclass clazz) { - return env->NewStringUTF(i2p::android::start().c_str()); + (JNIEnv * env, jclass clazz, jobjectArray args) { + int argc = env->GetArrayLength(args); + typedef char *pchar; + pchar* argv = new pchar[argc]; + for (int i = 0; i < argc; i++) { + jstring arg = (jstring) env->GetObjectArrayElement(args, i); + const char *argStr = env->GetStringUTFChars(arg, 0); + size_t len = strlen(argStr); + argv[i] = new char[len + 1]; + strcpy(argv[i], argStr); + env->ReleaseStringUTFChars(arg, argStr); + } + const char* result = i2p::android::start(argc,argv).c_str(); + for (int i = 0; i < argc; i++) { + delete [] argv[i]; + } + delete [] argv; + return env->NewStringUTF(result); } JNIEXPORT void JNICALL Java_org_purplei2p_i2pd_I2PD_1JNI_stopDaemon diff --git a/android/jni/org_purplei2p_i2pd_I2PD_JNI.h b/android/jni/org_purplei2p_i2pd_I2PD_JNI.h index 04923d22..484b3230 100644 --- a/android/jni/org_purplei2p_i2pd_I2PD_JNI.h +++ b/android/jni/org_purplei2p_i2pd_I2PD_JNI.h @@ -16,7 +16,7 @@ JNIEXPORT jstring JNICALL Java_org_purplei2p_i2pd_I2PD_1JNI_getABICompiledWith (JNIEnv *, jclass); JNIEXPORT jstring JNICALL Java_org_purplei2p_i2pd_I2PD_1JNI_startDaemon - (JNIEnv *, jclass); + (JNIEnv *, jclass, jobjectArray args); JNIEXPORT void JNICALL Java_org_purplei2p_i2pd_I2PD_1JNI_stopDaemon (JNIEnv *, jclass); diff --git a/android/src/org/purplei2p/i2pd/DaemonSingleton.java b/android/src/org/purplei2p/i2pd/DaemonSingleton.java index 65afd0f5..beff0c39 100644 --- a/android/src/org/purplei2p/i2pd/DaemonSingleton.java +++ b/android/src/org/purplei2p/i2pd/DaemonSingleton.java @@ -14,10 +14,10 @@ public class DaemonSingleton { public static DaemonSingleton getInstance() { return instance; } - + public synchronized void addStateChangeListener(StateUpdateListener listener) { stateUpdateListeners.add(listener); } public synchronized void removeStateChangeListener(StateUpdateListener listener) { stateUpdateListeners.remove(listener); } - + public synchronized void stopAcceptingTunnels() { if(isStartedOkay()){ state=State.gracefulShutdownInProgress; @@ -25,20 +25,21 @@ public class DaemonSingleton { I2PD_JNI.stopAcceptingTunnels(); } } - + public void onNetworkStateChange(boolean isConnected) { I2PD_JNI.onNetworkStateChanged(isConnected); } - + private boolean startedOkay; public static enum State {uninitialized,starting,jniLibraryLoaded,startedOkay,startFailed,gracefulShutdownInProgress}; - + private State state = State.uninitialized; - + public State getState() { return state; } + + public synchronized void start(final String confDir, final String dataDir) { - public synchronized void start() { if(state != State.uninitialized)return; state = State.starting; fireStateUpdate(); @@ -62,7 +63,15 @@ public class DaemonSingleton { } try { synchronized (DaemonSingleton.this) { - daemonStartResult = I2PD_JNI.startDaemon(); + + String args[] = new String[] { + "i2pd", "--service", "--daemon", + "--datadir=" + dataDir, + "--conf=" + confDir + "/i2pd.conf", + "--tunconf=" + confDir + "/tunnels.conf" + }; + + daemonStartResult = I2PD_JNI.startDaemon(args); if("ok".equals(daemonStartResult)){ state=State.startedOkay; setStartedOkay(true); @@ -76,9 +85,9 @@ public class DaemonSingleton { fireStateUpdate(); } return; - } + } } - + }, "i2pdDaemonStart").start(); } private Throwable lastThrowable; @@ -87,10 +96,10 @@ public class DaemonSingleton { private synchronized void fireStateUpdate() { Log.i(TAG, "daemon state change: "+state); for(StateUpdateListener listener : stateUpdateListeners) { - try { - listener.daemonStateUpdate(); - } catch (Throwable tr) { - Log.e(TAG, "exception in listener ignored", tr); + try { + listener.daemonStateUpdate(); + } catch (Throwable tr) { + Log.e(TAG, "exception in listener ignored", tr); } } } @@ -102,7 +111,7 @@ public class DaemonSingleton { public String getDaemonStartResult() { return daemonStartResult; } - + private final Object startedOkayLock = new Object(); public boolean isStartedOkay() { diff --git a/android/src/org/purplei2p/i2pd/Decompress.java b/android/src/org/purplei2p/i2pd/Decompress.java new file mode 100644 index 00000000..917abc7c --- /dev/null +++ b/android/src/org/purplei2p/i2pd/Decompress.java @@ -0,0 +1,83 @@ +package org.purplei2p.i2pd; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; + +import android.content.Context; +import android.util.Log; + +public class Decompress { + private static final int BUFFER_SIZE = 1024 * 10; + private static final String TAG = "Decompress"; + + public static void unzipFromAssets(Context context, String zipFile, String destination) { + try { + if (destination == null || destination.length() == 0) + destination = context.getFilesDir().getAbsolutePath(); + InputStream stream = context.getAssets().open(zipFile); + unzip(stream, destination); + } catch (IOException e) { + e.printStackTrace(); + } + } + + public static void unzip(String zipFile, String location) { + try { + FileInputStream fin = new FileInputStream(zipFile); + unzip(fin, location); + } catch (FileNotFoundException e) { + e.printStackTrace(); + } + + } + + public static void unzip(InputStream stream, String destination) { + dirChecker(destination, ""); + byte[] buffer = new byte[BUFFER_SIZE]; + try { + ZipInputStream zin = new ZipInputStream(stream); + ZipEntry ze = null; + + while ((ze = zin.getNextEntry()) != null) { + Log.v(TAG, "Unzipping " + ze.getName()); + + if (ze.isDirectory()) { + dirChecker(destination, ze.getName()); + } else { + File f = new File(destination + ze.getName()); + if (!f.exists()) { + FileOutputStream fout = new FileOutputStream(destination + ze.getName()); + int count; + while ((count = zin.read(buffer)) != -1) { + fout.write(buffer, 0, count); + } + zin.closeEntry(); + fout.close(); + } + } + + } + zin.close(); + } catch (Exception e) { + Log.e(TAG, "unzip", e); + } + + } + + private static void dirChecker(String destination, String dir) { + File f = new File(destination + dir); + + if (!f.isDirectory()) { + boolean success = f.mkdirs(); + if (!success) { + Log.w(TAG, "Failed to create folder " + f.getName()); + } + } + } +} diff --git a/android/src/org/purplei2p/i2pd/ForegroundService.java b/android/src/org/purplei2p/i2pd/ForegroundService.java index bfd650c8..645d0dca 100644 --- a/android/src/org/purplei2p/i2pd/ForegroundService.java +++ b/android/src/org/purplei2p/i2pd/ForegroundService.java @@ -5,6 +5,8 @@ import android.app.NotificationManager; import android.app.PendingIntent; import android.app.Service; import android.content.Intent; +import android.content.Context; +import android.os.Environment; import android.os.Binder; import android.os.IBinder; import android.util.Log; @@ -28,13 +30,20 @@ public class ForegroundService extends Service { } } + private String dataDir; + private String confDir; + @Override public void onCreate() { notificationManager = (NotificationManager)getSystemService(NOTIFICATION_SERVICE); + dataDir = this.getDir("data", Context.MODE_PRIVATE).toString(); + confDir = Environment.getExternalStoragePublicDirectory("i2pd").toString(); // Display a notification about us starting. We put an icon in the status bar. showNotification(); - daemon.start(); + + Log.i("ForegroundService", "About to start daemon with dataDir: " + dataDir + ", confDir: " + confDir); + daemon.start(confDir, dataDir); // Tell the user we started. Toast.makeText(this, R.string.i2pd_service_started, Toast.LENGTH_SHORT).show(); } @@ -42,7 +51,7 @@ public class ForegroundService extends Service { @Override public int onStartCommand(Intent intent, int flags, int startId) { Log.i("ForegroundService", "Received start id " + startId + ": " + intent); - daemon.start(); + daemon.start(confDir, dataDir); return START_STICKY; } @@ -50,7 +59,7 @@ public class ForegroundService extends Service { public void onDestroy() { // Cancel the persistent notification. notificationManager.cancel(NOTIFICATION); - + stopForeground(true); // Tell the user we stopped. @@ -91,7 +100,7 @@ public class ForegroundService extends Service { //mNM.notify(NOTIFICATION, notification); startForeground(NOTIFICATION, notification); } - + private final DaemonSingleton daemon = DaemonSingleton.getInstance(); } diff --git a/android/src/org/purplei2p/i2pd/I2PD.java b/android/src/org/purplei2p/i2pd/I2PD.java index a2494b2b..d66a0174 100755 --- a/android/src/org/purplei2p/i2pd/I2PD.java +++ b/android/src/org/purplei2p/i2pd/I2PD.java @@ -2,6 +2,13 @@ package org.purplei2p.i2pd; import java.io.PrintWriter; import java.io.StringWriter; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; + import java.util.Timer; import java.util.TimerTask; @@ -24,12 +31,12 @@ public class I2PD extends Activity { private static final String TAG = "i2pd"; private TextView textView; - + private final DaemonSingleton daemon = DaemonSingleton.getInstance(); private DaemonSingleton.StateUpdateListener daemonStateUpdatedListener = new DaemonSingleton.StateUpdateListener() { - + @Override public void daemonStateUpdate() { runOnUiThread(new Runnable(){ @@ -53,11 +60,14 @@ public class I2PD extends Activity { }); } }; - + @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + //install certs every time + Decompress.unzipFromAssets(this, "certificates.zip", this.getDir("data", Context.MODE_PRIVATE).toString() + "/" ); + textView = new TextView(this); setContentView(textView); DaemonSingleton.getInstance().addStateChangeListener(daemonStateUpdatedListener); @@ -123,7 +133,7 @@ public class I2PD extends Activity { } }; - + private boolean mIsBound; private void doBindService() { @@ -147,7 +157,7 @@ public class I2PD extends Activity { @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. - getMenuInflater().inflate(R.menu.options_main, menu); + getMenuInflater().inflate(R.menu.options_main, menu); return true; } @@ -216,9 +226,9 @@ public class I2PD extends Activity { @Override public void run() { - quit(); + quit(); } - + }, 10*60*1000/*milliseconds*/); }else{ quit(); @@ -227,7 +237,7 @@ public class I2PD extends Activity { Log.e(TAG,"",tr); } } - + },"gracQuitInit").start(); } diff --git a/android/src/org/purplei2p/i2pd/I2PD_JNI.java b/android/src/org/purplei2p/i2pd/I2PD_JNI.java index f965d471..bfdf8967 100644 --- a/android/src/org/purplei2p/i2pd/I2PD_JNI.java +++ b/android/src/org/purplei2p/i2pd/I2PD_JNI.java @@ -6,12 +6,12 @@ public class I2PD_JNI { * returns error info if failed * returns "ok" if daemon initialized and started okay */ - public static native String startDaemon(); + public static native String startDaemon(String args[]); //should only be called after startDaemon() success public static native void stopDaemon(); - + public static native void stopAcceptingTunnels(); - + public static native void onNetworkStateChanged(boolean isConnected); public static void loadLibraries() {