mirror of
https://github.com/PurpleI2P/i2pd.git
synced 2025-01-08 22:57:52 +00:00
Merge pull request #1564 from nonlinear-chaos-order-etc-etal/openssl
android fixes
This commit is contained in:
commit
1b63c9f6ad
@ -15,10 +15,10 @@
|
|||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
android:icon="@drawable/icon"
|
android:icon="@drawable/icon"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
android:theme="@android:style/Theme.Holo.Light.DarkActionBar"
|
|
||||||
android:requestLegacyExternalStorage="true"
|
android:requestLegacyExternalStorage="true"
|
||||||
android:usesCleartextTraffic="true"
|
android:theme="@android:style/Theme.Holo.Light.DarkActionBar"
|
||||||
>
|
android:usesCleartextTraffic="true">
|
||||||
|
<activity android:name=".WebConsoleActivity"></activity>
|
||||||
|
|
||||||
<receiver android:name=".NetworkStateChangeReceiver">
|
<receiver android:name=".NetworkStateChangeReceiver">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
@ -31,10 +31,10 @@
|
|||||||
android:label="@string/app_name">
|
android:label="@string/app_name">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".I2PDActivity"
|
android:name=".I2PDActivity"
|
||||||
android:label="@string/app_name" />
|
android:label="@string/app_name" />
|
||||||
@ -52,4 +52,5 @@
|
|||||||
android:value="org.purplei2p.i2pd.I2PDPermsAskerActivity" />
|
android:value="org.purplei2p.i2pd.I2PDPermsAskerActivity" />
|
||||||
</activity>
|
</activity>
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
@ -21,6 +21,8 @@ repositories {
|
|||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation 'androidx.core:core:1.0.2'
|
implementation 'androidx.core:core:1.0.2'
|
||||||
|
implementation 'androidx.appcompat:appcompat:1.0.2'
|
||||||
|
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
|
||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
|
@ -1,12 +1,14 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<LinearLayout android:id="@+id/layout_prompt"
|
<LinearLayout android:id="@+id/layout_prompt"
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="fill_parent"
|
android:layout_width="fill_parent"
|
||||||
android:layout_height="fill_parent"
|
android:layout_height="fill_parent"
|
||||||
tools:context=".I2PDActivity">
|
tools:context=".WebConsoleActivity">
|
||||||
|
|
||||||
<WebView
|
<WebView
|
||||||
android:id="@+id/webview1"
|
android:id="@+id/webview1"
|
||||||
android:layout_width="fill_parent"
|
android:layout_width="fill_parent"
|
||||||
android:layout_height="fill_parent" />
|
android:layout_height="fill_parent" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
374
android/src/org/purplei2p/i2pd/DaemonWrapper.java
Normal file
374
android/src/org/purplei2p/i2pd/DaemonWrapper.java
Normal file
@ -0,0 +1,374 @@
|
|||||||
|
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.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(State oldValue, State newValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
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(oldState, newState);
|
||||||
|
}
|
||||||
|
|
||||||
|
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 int getTransitTunnelsCount() {
|
||||||
|
return I2PD_JNI.GetTransitTunnelsCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isStartedOkay() {
|
||||||
|
return equals(State.startedOkay) || equals(State.gracefulShutdownInProgress);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
} 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(State oldValue, State newValue) {
|
||||||
|
Log.i(TAG, "daemon state change: " + state);
|
||||||
|
for (StateUpdateListener listener : stateUpdateListeners) {
|
||||||
|
try {
|
||||||
|
listener.daemonStateUpdate(oldValue, newValue);
|
||||||
|
} catch (Throwable tr) {
|
||||||
|
Log.e(TAG, "exception in listener ignored", tr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Throwable getLastThrowable() {
|
||||||
|
return lastThrowable;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDaemonStartResult() {
|
||||||
|
return daemonStartResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isStartedOkay() {
|
||||||
|
return getState().isStartedOkay();
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void stopDaemon() {
|
||||||
|
if (isStartedOkay()) {
|
||||||
|
try {
|
||||||
|
I2PD_JNI.stopDaemon();
|
||||||
|
} catch(Throwable tr) {
|
||||||
|
Log.e(TAG, "", tr);
|
||||||
|
}
|
||||||
|
|
||||||
|
setState(State.stopped);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void processAssets() {
|
||||||
|
if (!assetsCopied) {
|
||||||
|
try {
|
||||||
|
assetsCopied = true;
|
||||||
|
|
||||||
|
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) {
|
||||||
|
// If we have a directory, we make it and recurse. If a file, we copy its
|
||||||
|
// contents.
|
||||||
|
try {
|
||||||
|
String[] contents = assetManager.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() + "'");
|
||||||
|
}
|
||||||
|
|
||||||
|
private 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,13 +19,24 @@ 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 static final Object initDeinitLock = new Object();
|
||||||
|
|
||||||
|
private final DaemonWrapper.StateUpdateListener daemonStateUpdatedListener =
|
||||||
|
new DaemonWrapper.StateUpdateListener() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void daemonStateUpdate() {
|
public void daemonStateUpdate(DaemonWrapper.State oldValue, DaemonWrapper.State newValue) {
|
||||||
|
updateNotificationText();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private void updateNotificationText() {
|
||||||
try {
|
try {
|
||||||
synchronized (ForegroundService.this) {
|
synchronized (initDeinitLock) {
|
||||||
if (shown) cancelNotification();
|
if (shown) cancelNotification();
|
||||||
showNotification();
|
showNotification();
|
||||||
}
|
}
|
||||||
@ -33,14 +44,13 @@ public class ForegroundService extends Service {
|
|||||||
Log.e(TAG,"error ignored",tr);
|
Log.e(TAG,"error ignored",tr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
private NotificationManager notificationManager;
|
private NotificationManager notificationManager;
|
||||||
|
|
||||||
// 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,16 +63,27 @@ public class ForegroundService extends Service {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void init(DaemonWrapper daemon) {
|
||||||
|
ForegroundService.daemon = daemon;
|
||||||
|
initCheck();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void initCheck() {
|
||||||
|
synchronized (initDeinitLock) {
|
||||||
|
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;
|
||||||
synchronized (this) {
|
initCheck();
|
||||||
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);
|
||||||
|
updateNotificationText();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -73,11 +94,24 @@ public class ForegroundService extends Service {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDestroy() {
|
public void onDestroy() {
|
||||||
DaemonSingleton.getInstance().removeStateChangeListener(daemonStateUpdatedListener);
|
|
||||||
cancelNotification();
|
cancelNotification();
|
||||||
|
deinitCheck();
|
||||||
|
instance=null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private synchronized void cancelNotification() {
|
public static void deinit() {
|
||||||
|
deinitCheck();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void deinitCheck() {
|
||||||
|
synchronized (initDeinitLock) {
|
||||||
|
if (daemon != null && instance != null)
|
||||||
|
daemon.removeStateChangeListener(instance.daemonStateUpdatedListener);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void cancelNotification() {
|
||||||
|
synchronized (initDeinitLock) {
|
||||||
// Cancel the persistent notification.
|
// Cancel the persistent notification.
|
||||||
notificationManager.cancel(NOTIFICATION);
|
notificationManager.cancel(NOTIFICATION);
|
||||||
|
|
||||||
@ -87,6 +121,7 @@ public class ForegroundService extends Service {
|
|||||||
//Toast.makeText(this, R.string.i2pd_service_stopped, Toast.LENGTH_SHORT).show();
|
//Toast.makeText(this, R.string.i2pd_service_stopped, Toast.LENGTH_SHORT).show();
|
||||||
shown = false;
|
shown = false;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public IBinder onBind(Intent intent) {
|
public IBinder onBind(Intent intent) {
|
||||||
@ -100,9 +135,11 @@ 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 void showNotification() {
|
||||||
|
synchronized (initDeinitLock) {
|
||||||
|
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 +153,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 +170,8 @@ 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 +185,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();
|
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
@ -15,7 +7,6 @@ import java.util.TimerTask;
|
|||||||
|
|
||||||
import android.Manifest;
|
import android.Manifest;
|
||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
import android.annotation.TargetApi;
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.app.AlertDialog;
|
import android.app.AlertDialog;
|
||||||
import android.content.ActivityNotFoundException;
|
import android.content.ActivityNotFoundException;
|
||||||
@ -24,16 +15,11 @@ 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.NetworkCapabilities;
|
|
||||||
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;
|
||||||
@ -46,7 +32,6 @@ import android.widget.Toast;
|
|||||||
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.RequiresApi;
|
|
||||||
import androidx.core.app.ActivityCompat;
|
import androidx.core.app.ActivityCompat;
|
||||||
import androidx.core.content.ContextCompat;
|
import androidx.core.content.ContextCompat;
|
||||||
|
|
||||||
@ -60,25 +45,24 @@ 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(DaemonWrapper.State oldValue, DaemonWrapper.State newValue) {
|
||||||
processAssets();
|
updateStatusText();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private void updateStatusText() {
|
||||||
runOnUiThread(() -> {
|
runOnUiThread(() -> {
|
||||||
try {
|
try {
|
||||||
if (textView == null)
|
if (textView == null)
|
||||||
@ -88,16 +72,16 @@ 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);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
|
||||||
private static volatile long graceStartedMillis;
|
private static volatile long graceStartedMillis;
|
||||||
private static final Object graceStartedMillis_LOCK = new Object();
|
private static final Object graceStartedMillis_LOCK = new Object();
|
||||||
private Menu optionsMenu;
|
private Menu optionsMenu;
|
||||||
@ -117,10 +101,16 @@ 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);
|
||||||
daemonStateUpdatedListener.daemonStateUpdate();
|
daemonStateUpdatedListener.daemonStateUpdate(DaemonWrapper.State.uninitialized, daemon.getState());
|
||||||
|
|
||||||
// request permissions
|
// request permissions
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||||
@ -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 {
|
||||||
@ -288,15 +276,8 @@ public class I2PDActivity extends Activity {
|
|||||||
return true;
|
return true;
|
||||||
|
|
||||||
case R.id.action_start_webview:
|
case R.id.action_start_webview:
|
||||||
setContentView(R.layout.webview);
|
startActivity(new Intent(getApplicationContext(), WebConsoleActivity.class));
|
||||||
this.webView = (WebView) findViewById(R.id.webview1);
|
return true;
|
||||||
this.webView.setWebViewClient(new WebViewClient());
|
|
||||||
|
|
||||||
WebSettings webSettings = this.webView.getSettings();
|
|
||||||
webSettings.setBuiltInZoomControls(true);
|
|
||||||
webSettings.setJavaScriptEnabled(false);
|
|
||||||
this.webView.loadUrl("http://127.0.0.1:7070"); // TODO: instead 7070 I2Pd....HttpPort
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return super.onOptionsItemSelected(item);
|
return super.onOptionsItemSelected(item);
|
||||||
@ -335,7 +316,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;
|
||||||
}
|
}
|
||||||
@ -384,9 +365,10 @@ public class I2PDActivity extends Activity {
|
|||||||
if (gracefulQuitTimerOld != null)
|
if (gracefulQuitTimerOld != null)
|
||||||
gracefulQuitTimerOld.cancel();
|
gracefulQuitTimerOld.cancel();
|
||||||
|
|
||||||
if(daemon.GetTransitTunnelsCount() <= 0) { // no tunnels left
|
if(daemon.getTransitTunnelsCount() <= 0) { // no tunnels left
|
||||||
Log.d(TAG, "no transit tunnels left, stopping");
|
Log.d(TAG, "no transit tunnels left, stopping");
|
||||||
i2pdStop();
|
i2pdStop();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final Timer gracefulQuitTimer = new Timer(true);
|
final Timer gracefulQuitTimer = new Timer(true);
|
||||||
@ -402,7 +384,7 @@ public class I2PDActivity extends Activity {
|
|||||||
final TimerTask tickerTask = new TimerTask() {
|
final TimerTask tickerTask = new TimerTask() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
daemonStateUpdatedListener.daemonStateUpdate();
|
updateStatusText();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
gracefulQuitTimer.scheduleAtFixedRate(tickerTask, 0/*start delay*/, 1000/*millis period*/);
|
gracefulQuitTimer.scheduleAtFixedRate(tickerTask, 0/*start delay*/, 1000/*millis period*/);
|
||||||
@ -427,167 +409,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 +463,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) {
|
||||||
|
41
android/src/org/purplei2p/i2pd/WebConsoleActivity.java
Normal file
41
android/src/org/purplei2p/i2pd/WebConsoleActivity.java
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
package org.purplei2p.i2pd;
|
||||||
|
|
||||||
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.view.MenuItem;
|
||||||
|
import android.webkit.WebSettings;
|
||||||
|
import android.webkit.WebView;
|
||||||
|
import android.webkit.WebViewClient;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
public class WebConsoleActivity extends Activity {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
setContentView(R.layout.activity_web_console);
|
||||||
|
|
||||||
|
Objects.requireNonNull(getActionBar()).setDisplayHomeAsUpEnabled(true);
|
||||||
|
|
||||||
|
final WebView webView = findViewById(R.id.webview1);
|
||||||
|
webView.setWebViewClient(new WebViewClient());
|
||||||
|
|
||||||
|
final WebSettings webSettings = webView.getSettings();
|
||||||
|
webSettings.setBuiltInZoomControls(true);
|
||||||
|
webSettings.setJavaScriptEnabled(false);
|
||||||
|
webView.loadUrl("http://127.0.0.1:7070"); // TODO: instead 7070 I2Pd....HttpPort
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
|
int id = item.getItemId();
|
||||||
|
|
||||||
|
if (id==android.R.id.home) {
|
||||||
|
finish();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user