Browse Source

android: fix for pre-init jni calls; processAssets moved to a logical place

pull/1564/head
user 4 years ago
parent
commit
b6175132eb
  1. 181
      android/src/org/purplei2p/i2pd/DaemonSingleton.java
  2. 387
      android/src/org/purplei2p/i2pd/DaemonWrapper.java
  3. 52
      android/src/org/purplei2p/i2pd/ForegroundService.java
  4. 236
      android/src/org/purplei2p/i2pd/I2PDActivity.java

181
android/src/org/purplei2p/i2pd/DaemonSingleton.java

@ -1,181 +0,0 @@
package org.purplei2p.i2pd;
import java.util.HashSet;
import java.util.Set;
import android.os.Environment;
import android.util.Log;
import org.purplei2p.i2pd.R;
public class DaemonSingleton {
private static final String TAG = "i2pd";
private static final DaemonSingleton instance = new DaemonSingleton();
public interface StateUpdateListener {
void daemonStateUpdate();
}
private final Set<StateUpdateListener> stateUpdateListeners = new HashSet<>();
public static DaemonSingleton getInstance() {
return instance;
}
public synchronized void addStateChangeListener(StateUpdateListener listener) {
stateUpdateListeners.add(listener);
}
public synchronized void removeStateChangeListener(StateUpdateListener listener) {
stateUpdateListeners.remove(listener);
}
private synchronized void setState(State newState) {
if (newState == null)
throw new NullPointerException();
State oldState = state;
if (oldState == null)
throw new NullPointerException();
if (oldState.equals(newState))
return;
state = newState;
fireStateUpdate1();
}
public synchronized void stopAcceptingTunnels() {
if (isStartedOkay()) {
setState(State.gracefulShutdownInProgress);
I2PD_JNI.stopAcceptingTunnels();
}
}
public synchronized void startAcceptingTunnels() {
if (isStartedOkay()) {
setState(State.startedOkay);
I2PD_JNI.startAcceptingTunnels();
}
}
public synchronized void reloadTunnelsConfigs() {
if (isStartedOkay()) {
I2PD_JNI.reloadTunnelsConfigs();
}
}
public synchronized int GetTransitTunnelsCount() {
return I2PD_JNI.GetTransitTunnelsCount();
}
private volatile boolean startedOkay;
public enum State {
uninitialized(R.string.uninitialized),
starting(R.string.starting),
jniLibraryLoaded(R.string.jniLibraryLoaded),
startedOkay(R.string.startedOkay),
startFailed(R.string.startFailed),
gracefulShutdownInProgress(R.string.gracefulShutdownInProgress),
stopped(R.string.stopped);
State(int statusStringResourceId) {
this.statusStringResourceId = statusStringResourceId;
}
private final int statusStringResourceId;
public int getStatusStringResourceId() {
return statusStringResourceId;
}
};
private volatile State state = State.uninitialized;
public State getState() {
return state;
}
{
setState(State.starting);
new Thread(new Runnable() {
@Override
public void run() {
try {
I2PD_JNI.loadLibraries();
setState(State.jniLibraryLoaded);
} catch (Throwable tr) {
lastThrowable = tr;
setState(State.startFailed);
return;
}
try {
synchronized (DaemonSingleton.this) {
I2PD_JNI.setDataDir(Environment.getExternalStorageDirectory().getAbsolutePath() + "/i2pd");
daemonStartResult = I2PD_JNI.startDaemon();
if ("ok".equals(daemonStartResult)) {
setState(State.startedOkay);
setStartedOkay(true);
} else
setState(State.startFailed);
}
} catch (Throwable tr) {
lastThrowable = tr;
setState(State.startFailed);
}
}
}, "i2pdDaemonStart").start();
}
private Throwable lastThrowable;
private String daemonStartResult = "N/A";
private void fireStateUpdate1() {
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);
}
}
}
public Throwable getLastThrowable() {
return lastThrowable;
}
public String getDaemonStartResult() {
return daemonStartResult;
}
private final Object startedOkayLock = new Object();
public boolean isStartedOkay() {
synchronized (startedOkayLock) {
return startedOkay;
}
}
private void setStartedOkay(boolean startedOkay) {
synchronized (startedOkayLock) {
this.startedOkay = startedOkay;
}
}
public synchronized void stopDaemon() {
if (isStartedOkay()) {
try {
I2PD_JNI.stopDaemon();
} catch(Throwable tr) {
Log.e(TAG, "", tr);
}
setStartedOkay(false);
setState(State.stopped);
}
}
}

387
android/src/org/purplei2p/i2pd/DaemonWrapper.java

@ -0,0 +1,387 @@
package org.purplei2p.i2pd;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.HashSet;
import java.util.Set;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.AssetManager;
import android.net.ConnectivityManager;
import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.NetworkRequest;
import android.os.Build;
import android.os.Environment;
import android.util.Log;
import androidx.annotation.RequiresApi;
public class DaemonWrapper {
private static final String TAG = "i2pd";
private final AssetManager assetManager;
private final ConnectivityManager connectivityManager;
private String i2pdpath = Environment.getExternalStorageDirectory().getAbsolutePath() + "/i2pd/";
private boolean assetsCopied;
public interface StateUpdateListener {
void daemonStateUpdate();
}
private final Set<StateUpdateListener> stateUpdateListeners = new HashSet<>();
public synchronized void addStateChangeListener(StateUpdateListener listener) {
stateUpdateListeners.add(listener);
}
public synchronized void removeStateChangeListener(StateUpdateListener listener) {
stateUpdateListeners.remove(listener);
}
private synchronized void setState(State newState) {
if (newState == null)
throw new NullPointerException();
State oldState = state;
if (oldState == null)
throw new NullPointerException();
if (oldState.equals(newState))
return;
state = newState;
fireStateUpdate1();
}
public synchronized void stopAcceptingTunnels() {
if (isStartedOkay()) {
setState(State.gracefulShutdownInProgress);
I2PD_JNI.stopAcceptingTunnels();
}
}
public synchronized void startAcceptingTunnels() {
if (isStartedOkay()) {
setState(State.startedOkay);
I2PD_JNI.startAcceptingTunnels();
}
}
public synchronized void reloadTunnelsConfigs() {
if (isStartedOkay()) {
I2PD_JNI.reloadTunnelsConfigs();
}
}
public synchronized int GetTransitTunnelsCount() {
return I2PD_JNI.GetTransitTunnelsCount();
}
private volatile boolean startedOkay;
public enum State {
uninitialized(R.string.uninitialized),
starting(R.string.starting),
jniLibraryLoaded(R.string.jniLibraryLoaded),
startedOkay(R.string.startedOkay),
startFailed(R.string.startFailed),
gracefulShutdownInProgress(R.string.gracefulShutdownInProgress),
stopped(R.string.stopped);
State(int statusStringResourceId) {
this.statusStringResourceId = statusStringResourceId;
}
private final int statusStringResourceId;
public int getStatusStringResourceId() {
return statusStringResourceId;
}
};
private volatile State state = State.uninitialized;
public State getState() {
return state;
}
public DaemonWrapper(AssetManager assetManager, ConnectivityManager connectivityManager){
this.assetManager = assetManager;
this.connectivityManager = connectivityManager;
setState(State.starting);
new Thread(() -> {
try {
processAssets();
I2PD_JNI.loadLibraries();
setState(State.jniLibraryLoaded);
registerNetworkCallback();
} catch (Throwable tr) {
lastThrowable = tr;
setState(State.startFailed);
return;
}
try {
synchronized (DaemonWrapper.this) {
I2PD_JNI.setDataDir(Environment.getExternalStorageDirectory().getAbsolutePath() + "/i2pd");
daemonStartResult = I2PD_JNI.startDaemon();
if ("ok".equals(daemonStartResult)) {
setState(State.startedOkay);
setStartedOkay(true);
} else
setState(State.startFailed);
}
} catch (Throwable tr) {
lastThrowable = tr;
setState(State.startFailed);
}
}, "i2pdDaemonStart").start();
}
private Throwable lastThrowable;
private String daemonStartResult = "N/A";
private void fireStateUpdate1() {
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);
}
}
}
public Throwable getLastThrowable() {
return lastThrowable;
}
public String getDaemonStartResult() {
return daemonStartResult;
}
private final Object startedOkayLock = new Object();
public boolean isStartedOkay() {
synchronized (startedOkayLock) {
return startedOkay;
}
}
private void setStartedOkay(boolean startedOkay) {
synchronized (startedOkayLock) {
this.startedOkay = startedOkay;
}
}
public synchronized void stopDaemon() {
if (isStartedOkay()) {
try {
I2PD_JNI.stopDaemon();
} catch(Throwable tr) {
Log.e(TAG, "", tr);
}
setStartedOkay(false);
setState(State.stopped);
}
}
private void processAssets() {
if (!assetsCopied) {
try {
assetsCopied = true; // prevent from running on every state update
File holderFile = new File(i2pdpath, "assets.ready");
String versionName = BuildConfig.VERSION_NAME; // here will be app version, like 2.XX.XX
StringBuilder text = new StringBuilder();
if (holderFile.exists()) {
try { // if holder file exists, read assets version string
FileReader fileReader = new FileReader(holderFile);
try {
BufferedReader br = new BufferedReader(fileReader);
try {
String line;
while ((line = br.readLine()) != null) {
text.append(line);
}
}finally {
try {
br.close();
} catch (IOException e) {
Log.e(TAG, "", e);
}
}
} finally {
try {
fileReader.close();
} catch (IOException e) {
Log.e(TAG, "", e);
}
}
} catch (IOException e) {
Log.e(TAG, "", e);
}
}
// if version differs from current app version or null, try to delete certificates folder
if (!text.toString().contains(versionName))
try {
boolean deleteResult = holderFile.delete();
if (!deleteResult)
Log.e(TAG, "holderFile.delete() returned " + deleteResult + ", absolute path='" + holderFile.getAbsolutePath() + "'");
File certPath = new File(i2pdpath, "certificates");
deleteRecursive(certPath);
}
catch (Throwable tr) {
Log.e(TAG, "", tr);
}
// copy assets. If processed file exists, it won't be overwritten
copyAsset("addressbook");
copyAsset("certificates");
copyAsset("tunnels.d");
copyAsset("i2pd.conf");
copyAsset("subscriptions.txt");
copyAsset("tunnels.conf");
// update holder file about successful copying
FileWriter writer = new FileWriter(holderFile);
try {
writer.append(versionName);
} finally {
try {
writer.close();
} catch (IOException e) {
Log.e(TAG,"on writer close", e);
}
}
}
catch (Throwable tr)
{
Log.e(TAG,"on assets copying", tr);
}
}
}
/**
* Copy the asset at the specified path to this app's data directory. If the
* asset is a directory, its contents are also copied.
*
* @param path
* Path to asset, relative to app's assets directory.
*/
private void copyAsset(String path) {
AssetManager manager = assetManager;
// If we have a directory, we make it and recurse. If a file, we copy its
// contents.
try {
String[] contents = manager.list(path);
// The documentation suggests that list throws an IOException, but doesn't
// say under what conditions. It'd be nice if it did so when the path was
// to a file. That doesn't appear to be the case. If the returned array is
// null or has 0 length, we assume the path is to a file. This means empty
// directories will get turned into files.
if (contents == null || contents.length == 0) {
copyFileAsset(path);
return;
}
// Make the directory.
File dir = new File(i2pdpath, path);
boolean result = dir.mkdirs();
Log.d(TAG, "dir.mkdirs() returned " + result);
// Recurse on the contents.
for (String entry : contents) {
copyAsset(path + '/' + entry);
}
} catch (IOException e) {
Log.e(TAG, "ex ignored for path='" + path + "'", e);
}
}
/**
* Copy the asset file specified by path to app's data directory. Assumes
* parent directories have already been created.
*
* @param path
* Path to asset, relative to app's assets directory.
*/
private void copyFileAsset(String path) {
File file = new File(i2pdpath, path);
if (!file.exists()) {
try {
try (InputStream in = assetManager.open(path)) {
try (OutputStream out = new FileOutputStream(file)) {
byte[] buffer = new byte[1024];
int read = in.read(buffer);
while (read != -1) {
out.write(buffer, 0, read);
read = in.read(buffer);
}
}
}
} catch (IOException e) {
Log.e(TAG, "", e);
}
}
}
private void deleteRecursive(File fileOrDirectory) {
if (fileOrDirectory.isDirectory()) {
File[] files = fileOrDirectory.listFiles();
if (files != null) {
for (File child : files) {
deleteRecursive(child);
}
}
}
boolean deleteResult = fileOrDirectory.delete();
if (!deleteResult)
Log.e(TAG, "fileOrDirectory.delete() returned " + deleteResult + ", absolute path='" + fileOrDirectory.getAbsolutePath() + "'");
}
public void registerNetworkCallback(){
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) registerNetworkCallback0();
}
@TargetApi(Build.VERSION_CODES.M)
private void registerNetworkCallback0() {
NetworkRequest request = new NetworkRequest.Builder()
.addCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)
.build();
NetworkStateCallbackImpl networkCallback = new NetworkStateCallbackImpl();
connectivityManager.registerNetworkCallback(request, networkCallback);
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
private static final class NetworkStateCallbackImpl extends ConnectivityManager.NetworkCallback {
@Override
public void onAvailable(Network network) {
super.onAvailable(network);
I2PD_JNI.onNetworkStateChanged(true);
Log.i(TAG, "NetworkCallback.onAvailable");
}
@Override
public void onLost(Network network) {
super.onLost(network);
I2PD_JNI.onNetworkStateChanged(false);
Log.i(TAG, " NetworkCallback.onLost");
}
}
}

52
android/src/org/purplei2p/i2pd/ForegroundService.java

@ -19,8 +19,12 @@ public class ForegroundService extends Service {
private volatile boolean shown; private volatile boolean shown;
private final DaemonSingleton.StateUpdateListener daemonStateUpdatedListener = private static ForegroundService instance;
new DaemonSingleton.StateUpdateListener() {
private static volatile DaemonWrapper daemon;
private final DaemonWrapper.StateUpdateListener daemonStateUpdatedListener =
new DaemonWrapper.StateUpdateListener() {
@Override @Override
public void daemonStateUpdate() { public void daemonStateUpdate() {
@ -40,7 +44,7 @@ public class ForegroundService extends Service {
// Unique Identification Number for the Notification. // Unique Identification Number for the Notification.
// We use it on Notification start, and to cancel it. // We use it on Notification start, and to cancel it.
private int NOTIFICATION = 1; private static final int NOTIFICATION = 1;
/** /**
* Class for clients to access. Because we know this service always * Class for clients to access. Because we know this service always
@ -53,17 +57,26 @@ public class ForegroundService extends Service {
} }
} }
public static void init(DaemonWrapper daemon) {
ForegroundService.daemon = daemon;
initCheck();
}
private static void initCheck() {
if(instance!=null && daemon!=null) instance.setListener();
}
@Override @Override
public void onCreate() { public void onCreate() {
notificationManager = (NotificationManager)getSystemService(NOTIFICATION_SERVICE); notificationManager = (NotificationManager)getSystemService(NOTIFICATION_SERVICE);
instance = this;
initCheck();
}
synchronized (this) { private void setListener() {
DaemonSingleton.getInstance().addStateChangeListener(daemonStateUpdatedListener); daemon.addStateChangeListener(daemonStateUpdatedListener);
if (!shown) daemonStateUpdatedListener.daemonStateUpdate(); if (!shown) daemonStateUpdatedListener.daemonStateUpdate();
} }
// Tell the user we started.
// Toast.makeText(this, R.string.i2pd_service_started, Toast.LENGTH_SHORT).show();
}
@Override @Override
public int onStartCommand(Intent intent, int flags, int startId) { public int onStartCommand(Intent intent, int flags, int startId) {
@ -73,8 +86,17 @@ public class ForegroundService extends Service {
@Override @Override
public void onDestroy() { public void onDestroy() {
DaemonSingleton.getInstance().removeStateChangeListener(daemonStateUpdatedListener);
cancelNotification(); cancelNotification();
deinitCheck();
instance=null;
}
public static void deinit() {
deinitCheck();
}
private static void deinitCheck() {
if(daemon!=null && instance!=null)daemon.removeStateChangeListener(instance.daemonStateUpdatedListener);
} }
private synchronized void cancelNotification() { private synchronized void cancelNotification() {
@ -101,8 +123,9 @@ public class ForegroundService extends Service {
* Show a notification while this service is running. * Show a notification while this service is running.
*/ */
private synchronized void showNotification() { private synchronized void showNotification() {
if(daemon!=null) {
// In this sample, we'll use the same text for the ticker and the expanded notification // In this sample, we'll use the same text for the ticker and the expanded notification
CharSequence text = getText(DaemonSingleton.getInstance().getState().getStatusStringResourceId()); CharSequence text = getText(daemon.getState().getStatusStringResourceId());
// The PendingIntent to launch our activity if the user selects this notification // The PendingIntent to launch our activity if the user selects this notification
PendingIntent contentIntent = PendingIntent.getActivity(this, 0, PendingIntent contentIntent = PendingIntent.getActivity(this, 0,
@ -116,8 +139,10 @@ public class ForegroundService extends Service {
NotificationCompat.Builder builder = new NotificationCompat.Builder(this, channelId) NotificationCompat.Builder builder = new NotificationCompat.Builder(this, channelId)
.setOngoing(true) .setOngoing(true)
.setSmallIcon(R.drawable.itoopie_notification_icon); // the status icon .setSmallIcon(R.drawable.itoopie_notification_icon); // the status icon
if(Build.VERSION.SDK_INT >= 16) builder = builder.setPriority(Notification.PRIORITY_DEFAULT); if (Build.VERSION.SDK_INT >= 16)
if(Build.VERSION.SDK_INT >= 21) builder = builder.setCategory(Notification.CATEGORY_SERVICE); builder = builder.setPriority(Notification.PRIORITY_DEFAULT);
if (Build.VERSION.SDK_INT >= 21)
builder = builder.setCategory(Notification.CATEGORY_SERVICE);
Notification notification = builder Notification notification = builder
.setTicker(text) // the status text .setTicker(text) // the status text
.setWhen(System.currentTimeMillis()) // the time stamp .setWhen(System.currentTimeMillis()) // the time stamp
@ -131,6 +156,7 @@ public class ForegroundService extends Service {
startForeground(NOTIFICATION, notification); startForeground(NOTIFICATION, notification);
shown = true; shown = true;
} }
}
@RequiresApi(Build.VERSION_CODES.O) @RequiresApi(Build.VERSION_CODES.O)
private synchronized String createNotificationChannel() { private synchronized String createNotificationChannel() {
@ -144,6 +170,4 @@ public class ForegroundService extends Service {
else Log.e(TAG, "error: NOTIFICATION_SERVICE is null"); else Log.e(TAG, "error: NOTIFICATION_SERVICE is null");
return channelId; return channelId;
} }
private static final DaemonSingleton daemon = DaemonSingleton.getInstance();
} }

236
android/src/org/purplei2p/i2pd/I2PDActivity.java

@ -1,13 +1,5 @@
package org.purplei2p.i2pd; package org.purplei2p.i2pd;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.BufferedReader;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.io.StringWriter; import java.io.StringWriter;
import java.util.Timer; import java.util.Timer;
@ -24,7 +16,6 @@ import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.ServiceConnection; import android.content.ServiceConnection;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.content.res.AssetManager;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.net.ConnectivityManager; import android.net.ConnectivityManager;
import android.net.Network; import android.net.Network;
@ -33,7 +24,6 @@ import android.net.NetworkRequest;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.os.Build; import android.os.Build;
import android.os.Environment;
import android.os.IBinder; import android.os.IBinder;
import android.os.PowerManager; import android.os.PowerManager;
import android.preference.PreferenceManager; import android.preference.PreferenceManager;
@ -60,25 +50,19 @@ import android.webkit.WebViewClient;
import static android.provider.Settings.ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS; import static android.provider.Settings.ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS;
public class I2PDActivity extends Activity { public class I2PDActivity extends Activity {
private WebView webView;
private static final String TAG = "i2pdActvt"; private static final String TAG = "i2pdActvt";
private static final int MY_PERMISSION_REQUEST_WRITE_EXTERNAL_STORAGE = 1; private static final int MY_PERMISSION_REQUEST_WRITE_EXTERNAL_STORAGE = 1;
public static final int GRACEFUL_DELAY_MILLIS = 10 * 60 * 1000; public static final int GRACEFUL_DELAY_MILLIS = 10 * 60 * 1000;
public static final String PACKAGE_URI_SCHEME = "package:"; public static final String PACKAGE_URI_SCHEME = "package:";
private TextView textView; private TextView textView;
private boolean assetsCopied; //private ConfigParser parser = new ConfigParser(i2pdpath); // TODO
private NetworkStateCallback networkCallback;
private String i2pdpath = Environment.getExternalStorageDirectory().getAbsolutePath() + "/i2pd/";
//private ConfigParser parser = new ConfigParser(i2pdpath); // TODO:
private static final DaemonSingleton daemon = DaemonSingleton.getInstance(); private static volatile DaemonWrapper daemon;
private final DaemonSingleton.StateUpdateListener daemonStateUpdatedListener = new DaemonSingleton.StateUpdateListener() { private final DaemonWrapper.StateUpdateListener daemonStateUpdatedListener = new DaemonWrapper.StateUpdateListener() {
@Override @Override
public void daemonStateUpdate() { public void daemonStateUpdate() {
processAssets();
runOnUiThread(() -> { runOnUiThread(() -> {
try { try {
if (textView == null) if (textView == null)
@ -88,9 +72,9 @@ public class I2PDActivity extends Activity {
textView.setText(throwableToString(tr)); textView.setText(throwableToString(tr));
return; return;
} }
DaemonSingleton.State state = daemon.getState(); DaemonWrapper.State state = daemon.getState();
String startResultStr = DaemonSingleton.State.startFailed.equals(state) ? String.format(": %s", daemon.getDaemonStartResult()) : ""; String startResultStr = DaemonWrapper.State.startFailed.equals(state) ? String.format(": %s", daemon.getDaemonStartResult()) : "";
String graceStr = DaemonSingleton.State.gracefulShutdownInProgress.equals(state) ? String.format(": %s %s", formatGraceTimeRemaining(), getText(R.string.remaining)) : ""; String graceStr = DaemonWrapper.State.gracefulShutdownInProgress.equals(state) ? String.format(": %s %s", formatGraceTimeRemaining(), getText(R.string.remaining)) : "";
textView.setText(String.format("%s%s%s", getText(state.getStatusStringResourceId()), startResultStr, graceStr)); textView.setText(String.format("%s%s%s", getText(state.getStatusStringResourceId()), startResultStr, graceStr));
} catch (Throwable tr) { } catch (Throwable tr) {
Log.e(TAG,"error ignored",tr); Log.e(TAG,"error ignored",tr);
@ -117,6 +101,12 @@ public class I2PDActivity extends Activity {
Log.i(TAG, "onCreate"); Log.i(TAG, "onCreate");
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
if (daemon==null) {
ConnectivityManager connectivityManager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
daemon = new DaemonWrapper(getAssets(), connectivityManager);
}
ForegroundService.init(daemon);
textView = new TextView(this); textView = new TextView(this);
setContentView(textView); setContentView(textView);
daemon.addStateChangeListener(daemonStateUpdatedListener); daemon.addStateChangeListener(daemonStateUpdatedListener);
@ -145,15 +135,13 @@ public class I2PDActivity extends Activity {
openBatteryOptimizationDialogIfNeeded(); openBatteryOptimizationDialogIfNeeded();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
registerNetworkCallback();
}
} }
@Override @Override
protected void onDestroy() { protected void onDestroy() {
super.onDestroy(); super.onDestroy();
textView = null; textView = null;
ForegroundService.deinit();
daemon.removeStateChangeListener(daemonStateUpdatedListener); daemon.removeStateChangeListener(daemonStateUpdatedListener);
//cancelGracefulStop0(); //cancelGracefulStop0();
try { try {
@ -289,13 +277,13 @@ public class I2PDActivity extends Activity {
case R.id.action_start_webview: case R.id.action_start_webview:
setContentView(R.layout.webview); setContentView(R.layout.webview);
this.webView = (WebView) findViewById(R.id.webview1); final WebView webView = findViewById(R.id.webview1);
this.webView.setWebViewClient(new WebViewClient()); webView.setWebViewClient(new WebViewClient());
WebSettings webSettings = this.webView.getSettings(); final WebSettings webSettings = webView.getSettings();
webSettings.setBuiltInZoomControls(true); webSettings.setBuiltInZoomControls(true);
webSettings.setJavaScriptEnabled(false); webSettings.setJavaScriptEnabled(false);
this.webView.loadUrl("http://127.0.0.1:7070"); // TODO: instead 7070 I2Pd....HttpPort webView.loadUrl("http://127.0.0.1:7070"); // TODO: instead 7070 I2Pd....HttpPort
break; break;
} }
@ -335,7 +323,7 @@ public class I2PDActivity extends Activity {
private static volatile Timer gracefulQuitTimer; private static volatile Timer gracefulQuitTimer;
private void i2pdGracefulStop() { private void i2pdGracefulStop() {
if (daemon.getState() == DaemonSingleton.State.stopped) { if (daemon.getState() == DaemonWrapper.State.stopped) {
Toast.makeText(this, R.string.already_stopped, Toast.LENGTH_SHORT).show(); Toast.makeText(this, R.string.already_stopped, Toast.LENGTH_SHORT).show();
return; return;
} }
@ -427,167 +415,6 @@ public class I2PDActivity extends Activity {
}); });
} }
/**
* Copy the asset at the specified path to this app's data directory. If the
* asset is a directory, its contents are also copied.
*
* @param path
* Path to asset, relative to app's assets directory.
*/
private void copyAsset(String path) {
AssetManager manager = getAssets();
// If we have a directory, we make it and recurse. If a file, we copy its
// contents.
try {
String[] contents = manager.list(path);
// The documentation suggests that list throws an IOException, but doesn't
// say under what conditions. It'd be nice if it did so when the path was
// to a file. That doesn't appear to be the case. If the returned array is
// null or has 0 length, we assume the path is to a file. This means empty
// directories will get turned into files.
if (contents == null || contents.length == 0) {
copyFileAsset(path);
return;
}
// Make the directory.
File dir = new File(i2pdpath, path);
boolean result = dir.mkdirs();
Log.d(TAG, "dir.mkdirs() returned " + result);
// Recurse on the contents.
for (String entry : contents) {
copyAsset(path + '/' + entry);
}
} catch (IOException e) {
Log.e(TAG, "ex ignored for path='" + path + "'", e);
}
}
/**
* Copy the asset file specified by path to app's data directory. Assumes
* parent directories have already been created.
*
* @param path
* Path to asset, relative to app's assets directory.
*/
private void copyFileAsset(String path) {
File file = new File(i2pdpath, path);
if (!file.exists()) {
try {
try (InputStream in = getAssets().open(path)) {
try (OutputStream out = new FileOutputStream(file)) {
byte[] buffer = new byte[1024];
int read = in.read(buffer);
while (read != -1) {
out.write(buffer, 0, read);
read = in.read(buffer);
}
}
}
} catch (IOException e) {
Log.e(TAG, "", e);
}
}
}
private void deleteRecursive(File fileOrDirectory) {
if (fileOrDirectory.isDirectory()) {
File[] files = fileOrDirectory.listFiles();
if (files != null) {
for (File child : files) {
deleteRecursive(child);
}
}
}
boolean deleteResult = fileOrDirectory.delete();
if (!deleteResult)
Log.e(TAG, "fileOrDirectory.delete() returned " + deleteResult + ", absolute path='" + fileOrDirectory.getAbsolutePath() + "'");
}
private void processAssets() {
if (!assetsCopied) {
try {
assetsCopied = true; // prevent from running on every state update
File holderFile = new File(i2pdpath, "assets.ready");
String versionName = BuildConfig.VERSION_NAME; // here will be app version, like 2.XX.XX
StringBuilder text = new StringBuilder();
if (holderFile.exists()) {
try { // if holder file exists, read assets version string
FileReader fileReader = new FileReader(holderFile);
try {
BufferedReader br = new BufferedReader(fileReader);
try {
String line;
while ((line = br.readLine()) != null) {
text.append(line);
}
}finally {
try {
br.close();
} catch (IOException e) {
Log.e(TAG, "", e);
}
}
} finally {
try {
fileReader.close();
} catch (IOException e) {
Log.e(TAG, "", e);
}
}
} catch (IOException e) {
Log.e(TAG, "", e);
}
}
// if version differs from current app version or null, try to delete certificates folder
if (!text.toString().contains(versionName))
try {
boolean deleteResult = holderFile.delete();
if (!deleteResult)
Log.e(TAG, "holderFile.delete() returned " + deleteResult + ", absolute path='" + holderFile.getAbsolutePath() + "'");
File certPath = new File(i2pdpath, "certificates");
deleteRecursive(certPath);
}
catch (Throwable tr) {
Log.e(TAG, "", tr);
}
// copy assets. If processed file exists, it won't be overwritten
copyAsset("addressbook");
copyAsset("certificates");
copyAsset("tunnels.d");
copyAsset("i2pd.conf");
copyAsset("subscriptions.txt");
copyAsset("tunnels.conf");
// update holder file about successful copying
FileWriter writer = new FileWriter(holderFile);
try {
writer.append(versionName);
} finally {
try {
writer.close();
} catch (IOException e) {
Log.e(TAG,"on writer close", e);
}
}
}
catch (Throwable tr)
{
Log.e(TAG,"on assets copying", tr);
}
}
}
@SuppressLint("BatteryLife") @SuppressLint("BatteryLife")
private void openBatteryOptimizationDialogIfNeeded() { private void openBatteryOptimizationDialogIfNeeded() {
boolean questionEnabled = getPreferences().getBoolean(getBatteryOptimizationPreferenceKey(), true); boolean questionEnabled = getPreferences().getBoolean(getBatteryOptimizationPreferenceKey(), true);
@ -642,33 +469,6 @@ public class I2PDActivity extends Activity {
return "show_battery_optimization" + (device == null ? "" : device); return "show_battery_optimization" + (device == null ? "" : device);
} }
@TargetApi(Build.VERSION_CODES.M)
private void registerNetworkCallback() {
ConnectivityManager connectivityManager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkRequest request = new NetworkRequest.Builder()
.addCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)
.build();
networkCallback = new NetworkStateCallback();
connectivityManager.registerNetworkCallback(request, networkCallback);
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
private final class NetworkStateCallback extends ConnectivityManager.NetworkCallback {
@Override
public void onAvailable(Network network) {
super.onAvailable(network);
I2PD_JNI.onNetworkStateChanged(true);
Log.i(TAG, "NetworkCallback.onAvailable");
}
@Override
public void onLost(Network network) {
super.onLost(network);
I2PD_JNI.onNetworkStateChanged(false);
Log.i(TAG, " NetworkCallback.onLost");
}
}
private void quit() { private void quit() {
try { try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {

Loading…
Cancel
Save