diff --git a/android/src/org/purplei2p/i2pd/DaemonSingleton.java b/android/src/org/purplei2p/i2pd/DaemonSingleton.java deleted file mode 100644 index e9e4fc06..00000000 --- a/android/src/org/purplei2p/i2pd/DaemonSingleton.java +++ /dev/null @@ -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 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); - } - } -} diff --git a/android/src/org/purplei2p/i2pd/DaemonWrapper.java b/android/src/org/purplei2p/i2pd/DaemonWrapper.java new file mode 100644 index 00000000..7f9fcd93 --- /dev/null +++ b/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 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"); + } + } +} diff --git a/android/src/org/purplei2p/i2pd/ForegroundService.java b/android/src/org/purplei2p/i2pd/ForegroundService.java index c1b1cc26..c1c918ac 100644 --- a/android/src/org/purplei2p/i2pd/ForegroundService.java +++ b/android/src/org/purplei2p/i2pd/ForegroundService.java @@ -19,8 +19,12 @@ public class ForegroundService extends Service { private volatile boolean shown; - private final DaemonSingleton.StateUpdateListener daemonStateUpdatedListener = - new DaemonSingleton.StateUpdateListener() { + private static ForegroundService instance; + + private static volatile DaemonWrapper daemon; + + private final DaemonWrapper.StateUpdateListener daemonStateUpdatedListener = + new DaemonWrapper.StateUpdateListener() { @Override public void daemonStateUpdate() { @@ -40,7 +44,7 @@ public class ForegroundService extends Service { // Unique Identification Number for the Notification. // 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 @@ -53,16 +57,25 @@ 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 public void onCreate() { notificationManager = (NotificationManager)getSystemService(NOTIFICATION_SERVICE); + instance = this; + initCheck(); + } - synchronized (this) { - DaemonSingleton.getInstance().addStateChangeListener(daemonStateUpdatedListener); - if (!shown) daemonStateUpdatedListener.daemonStateUpdate(); - } - // Tell the user we started. -// Toast.makeText(this, R.string.i2pd_service_started, Toast.LENGTH_SHORT).show(); + private void setListener() { + daemon.addStateChangeListener(daemonStateUpdatedListener); + if (!shown) daemonStateUpdatedListener.daemonStateUpdate(); } @Override @@ -73,8 +86,17 @@ public class ForegroundService extends Service { @Override public void onDestroy() { - DaemonSingleton.getInstance().removeStateChangeListener(daemonStateUpdatedListener); 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() { @@ -101,35 +123,39 @@ public class ForegroundService extends Service { * Show a notification while this service is running. */ private synchronized void showNotification() { - // In this sample, we'll use the same text for the ticker and the expanded notification - CharSequence text = getText(DaemonSingleton.getInstance().getState().getStatusStringResourceId()); + if(daemon!=null) { + // In this sample, we'll use the same text for the ticker and the expanded notification + CharSequence text = getText(daemon.getState().getStatusStringResourceId()); - // The PendingIntent to launch our activity if the user selects this notification - PendingIntent contentIntent = PendingIntent.getActivity(this, 0, - new Intent(this, I2PDActivity.class), 0); + // The PendingIntent to launch our activity if the user selects this notification + PendingIntent contentIntent = PendingIntent.getActivity(this, 0, + new Intent(this, I2PDActivity.class), 0); - // If earlier version channel ID is not used - // https://developer.android.com/reference/android/support/v4/app/NotificationCompat.Builder.html#NotificationCompat.Builder(android.content.Context) - String channelId = Build.VERSION.SDK_INT >= 26 ? createNotificationChannel() : ""; + // If earlier version channel ID is not used + // https://developer.android.com/reference/android/support/v4/app/NotificationCompat.Builder.html#NotificationCompat.Builder(android.content.Context) + String channelId = Build.VERSION.SDK_INT >= 26 ? createNotificationChannel() : ""; - // Set the info for the views that show in the notification panel. - NotificationCompat.Builder builder = new NotificationCompat.Builder(this, channelId) - .setOngoing(true) - .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 >= 21) builder = builder.setCategory(Notification.CATEGORY_SERVICE); - Notification notification = builder - .setTicker(text) // the status text - .setWhen(System.currentTimeMillis()) // the time stamp - .setContentTitle(getText(R.string.app_name)) // the label of the entry - .setContentText(text) // the contents of the entry - .setContentIntent(contentIntent) // The intent to send when the entry is clicked - .build(); + // Set the info for the views that show in the notification panel. + NotificationCompat.Builder builder = new NotificationCompat.Builder(this, channelId) + .setOngoing(true) + .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 >= 21) + builder = builder.setCategory(Notification.CATEGORY_SERVICE); + Notification notification = builder + .setTicker(text) // the status text + .setWhen(System.currentTimeMillis()) // the time stamp + .setContentTitle(getText(R.string.app_name)) // the label of the entry + .setContentText(text) // the contents of the entry + .setContentIntent(contentIntent) // The intent to send when the entry is clicked + .build(); - // Send the notification. - //mNM.notify(NOTIFICATION, notification); - startForeground(NOTIFICATION, notification); - shown = true; + // Send the notification. + //mNM.notify(NOTIFICATION, notification); + startForeground(NOTIFICATION, notification); + shown = true; + } } @RequiresApi(Build.VERSION_CODES.O) @@ -144,6 +170,4 @@ public class ForegroundService extends Service { else Log.e(TAG, "error: NOTIFICATION_SERVICE is null"); return channelId; } - - private static final DaemonSingleton daemon = DaemonSingleton.getInstance(); } diff --git a/android/src/org/purplei2p/i2pd/I2PDActivity.java b/android/src/org/purplei2p/i2pd/I2PDActivity.java index 777ca748..0801a655 100755 --- a/android/src/org/purplei2p/i2pd/I2PDActivity.java +++ b/android/src/org/purplei2p/i2pd/I2PDActivity.java @@ -1,13 +1,5 @@ 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.StringWriter; import java.util.Timer; @@ -24,7 +16,6 @@ import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; import android.content.SharedPreferences; -import android.content.res.AssetManager; import android.content.pm.PackageManager; import android.net.ConnectivityManager; import android.net.Network; @@ -33,7 +24,6 @@ import android.net.NetworkRequest; import android.net.Uri; import android.os.Bundle; import android.os.Build; -import android.os.Environment; import android.os.IBinder; import android.os.PowerManager; import android.preference.PreferenceManager; @@ -60,25 +50,19 @@ import android.webkit.WebViewClient; import static android.provider.Settings.ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS; public class I2PDActivity extends Activity { - private WebView webView; - private static final String TAG = "i2pdActvt"; private static final int MY_PERMISSION_REQUEST_WRITE_EXTERNAL_STORAGE = 1; public static final int GRACEFUL_DELAY_MILLIS = 10 * 60 * 1000; public static final String PACKAGE_URI_SCHEME = "package:"; private TextView textView; - private boolean assetsCopied; - private NetworkStateCallback networkCallback; - private String i2pdpath = Environment.getExternalStorageDirectory().getAbsolutePath() + "/i2pd/"; - //private ConfigParser parser = new ConfigParser(i2pdpath); // TODO: + //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 public void daemonStateUpdate() { - processAssets(); runOnUiThread(() -> { try { if (textView == null) @@ -88,9 +72,9 @@ public class I2PDActivity extends Activity { textView.setText(throwableToString(tr)); return; } - DaemonSingleton.State state = daemon.getState(); - String startResultStr = DaemonSingleton.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)) : ""; + DaemonWrapper.State state = daemon.getState(); + String startResultStr = DaemonWrapper.State.startFailed.equals(state) ? String.format(": %s", daemon.getDaemonStartResult()) : ""; + 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)); } catch (Throwable tr) { Log.e(TAG,"error ignored",tr); @@ -117,6 +101,12 @@ public class I2PDActivity extends Activity { Log.i(TAG, "onCreate"); 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); setContentView(textView); daemon.addStateChangeListener(daemonStateUpdatedListener); @@ -145,15 +135,13 @@ public class I2PDActivity extends Activity { openBatteryOptimizationDialogIfNeeded(); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - registerNetworkCallback(); - } } @Override protected void onDestroy() { super.onDestroy(); textView = null; + ForegroundService.deinit(); daemon.removeStateChangeListener(daemonStateUpdatedListener); //cancelGracefulStop0(); try { @@ -289,13 +277,13 @@ public class I2PDActivity extends Activity { case R.id.action_start_webview: setContentView(R.layout.webview); - this.webView = (WebView) findViewById(R.id.webview1); - this.webView.setWebViewClient(new WebViewClient()); + final WebView webView = findViewById(R.id.webview1); + webView.setWebViewClient(new WebViewClient()); - WebSettings webSettings = this.webView.getSettings(); + final WebSettings webSettings = webView.getSettings(); webSettings.setBuiltInZoomControls(true); 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; } @@ -335,7 +323,7 @@ public class I2PDActivity extends Activity { private static volatile Timer gracefulQuitTimer; 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(); 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") private void openBatteryOptimizationDialogIfNeeded() { boolean questionEnabled = getPreferences().getBoolean(getBatteryOptimizationPreferenceKey(), true); @@ -642,33 +469,6 @@ public class I2PDActivity extends Activity { 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() { try { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {