mirror of
				https://github.com/PurpleI2P/i2pd.git
				synced 2025-11-04 08:30:46 +00:00 
			
		
		
		
	android: fix for pre-init jni calls; processAssets moved to a logical place
This commit is contained in:
		
							parent
							
								
									cb55944ff6
								
							
						
					
					
						commit
						b6175132eb
					
				
					 4 changed files with 466 additions and 436 deletions
				
			
		| 
						 | 
				
			
			@ -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
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										387
									
								
								android/src/org/purplei2p/i2pd/DaemonWrapper.java
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -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");
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -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();
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue