Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 7 additions & 13 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,6 @@
xmlns:tools="http://schemas.android.com/tools"
android:installLocation="auto">

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="29" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"
android:maxSdkVersion="29" />
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"
tools:ignore="ScopedStorage" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
Expand All @@ -19,7 +13,6 @@
android:allowBackup="true"
android:icon="@mipmap/logo"
android:label="@string/app_name"
android:requestLegacyExternalStorage="true"
android:theme="@android:style/Theme.DeviceDefault"
android:usesCleartextTraffic="true">

Expand Down Expand Up @@ -62,30 +55,31 @@
</receiver>

<activity
android:name=".I2PDPermsAskerActivity"
android:name=".I2PDActivity"
android:exported="true" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>

<activity
android:name=".I2PDActivity" />

<service
android:name=".ForegroundService"
android:exported="false"
android:stopWithTask="false"
android:enabled="true" />

<!-- Kept for compatibility; no longer used as launcher (data is app-private). -->
<activity
android:name=".I2PDPermsAskerActivity"
android:exported="false" />
<activity
android:name=".I2PDPermsExplanationActivity"
android:label="@string/title_activity_i2_pdperms_asker_prompt"
android:parentActivityName=".I2PDPermsAskerActivity">
android:parentActivityName=".I2PDActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="org.purplei2p.i2pd.I2PDPermsAskerActivity" />
android:value="org.purplei2p.i2pd.I2PDActivity" />
</activity>
<activity
android:name=".MainPreferenceActivity"
Expand Down
8 changes: 4 additions & 4 deletions app/src/main/assets/i2pd.conf
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
## See https://i2pd.readthedocs.io/en/latest/user-guide/configuration/
## for more options you can use in this file.

#logfile = /sdcard/i2pd/i2pd.log
#logfile = i2pd.log
loglevel = none
#tunnelsdir = /sdcard/i2pd/tunnels.d
#tunnelsdir = tunnels.d

# host = 1.2.3.4
# port = 4567
Expand Down Expand Up @@ -63,8 +63,8 @@ keys = proxy-keys.dat

[sam]
enabled = false
# address = 127.0.0.1
# port = 7656
address = 127.0.0.1
port = 7656

[precomputation]
elgamal = false
Expand Down
68 changes: 65 additions & 3 deletions app/src/main/java/org/purplei2p/i2pd/DaemonWrapper.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import java.util.Locale;

import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.AssetManager;
import android.net.ConnectivityManager;
import android.net.Network;
Expand All @@ -29,7 +30,8 @@ public class DaemonWrapper {
private static final String TAG = "i2pd";
private final AssetManager assetManager;
private final ConnectivityManager connectivityManager;
private String i2pdpath = Environment.getExternalStorageDirectory().getAbsolutePath() + "/i2pd";
/** App-private data dir (no MANAGE_EXTERNAL_STORAGE / all-files access). */
private String i2pdpath;
private boolean assetsCopied;

private static final String appLocale = Locale.getDefault().getDisplayLanguage(Locale.ENGLISH).toLowerCase(); // lower-case system language (like "english")
Expand Down Expand Up @@ -117,13 +119,73 @@ public State getState() {
return state;
}

public DaemonWrapper(AssetManager assetManager, ConnectivityManager connectivityManager){
public DaemonWrapper(Context context, AssetManager assetManager, ConnectivityManager connectivityManager){
this.assetManager = assetManager;
this.connectivityManager = connectivityManager;
Context appContext = context.getApplicationContext();
this.i2pdpath = new File(appContext.getFilesDir(), "i2pd").getAbsolutePath();
maybeMigrateFromLegacyExternalStorage();
setState(State.starting);
startDaemon();
}

/**
* One-time copy from legacy {@code /sdcard/i2pd} into app-private storage
* so existing installs keep keys/config after dropping all-files access.
*/
private void maybeMigrateFromLegacyExternalStorage() {
File newDir = new File(i2pdpath);
File newConf = new File(newDir, "i2pd.conf");
if (newConf.exists())
return;

File oldDir;
try {
oldDir = new File(Environment.getExternalStorageDirectory(), "i2pd");
} catch (Throwable t) {
Log.w(TAG, "legacy external storage unavailable", t);
return;
}
if (!oldDir.isDirectory())
return;

File oldConf = new File(oldDir, "i2pd.conf");
if (!oldConf.exists())
return;

Log.i(TAG, "migrating i2pd data from " + oldDir + " to " + newDir);
try {
copyDirectoryRecursive(oldDir, newDir);
} catch (Throwable t) {
Log.e(TAG, "legacy data migration failed", t);
}
}

private static void copyDirectoryRecursive(File src, File dst) throws IOException {
if (src.isDirectory()) {
if (!dst.exists() && !dst.mkdirs())
throw new IOException("mkdirs failed: " + dst);
String[] children = src.list();
if (children == null)
return;
for (String child : children) {
copyDirectoryRecursive(new File(src, child), new File(dst, child));
}
} else {
File parent = dst.getParentFile();
if (parent != null && !parent.exists() && !parent.mkdirs())
throw new IOException("mkdirs failed: " + parent);
try (InputStream in = new java.io.FileInputStream(src);
OutputStream out = new FileOutputStream(dst)) {
byte[] buffer = new byte[8192];
int read;
while ((read = in.read(buffer)) != -1) {
out.write(buffer, 0, read);
}
}
}
}

private Throwable lastThrowable;
private String daemonStartResult = "N/A";

Expand Down Expand Up @@ -184,7 +246,7 @@ public synchronized void startDaemon() {
}
try {
synchronized (DaemonWrapper.this) {
I2PD_JNI.setDataDir(i2pdpath); // (Environment.getExternalStorageDirectory().getAbsolutePath() + "/i2pd");
I2PD_JNI.setDataDir(i2pdpath);

Log.i(TAG, "setting webconsole language to " + appLocale);
I2PD_JNI.setLanguage(appLocale);
Expand Down
58 changes: 23 additions & 35 deletions app/src/main/java/org/purplei2p/i2pd/I2PDActivity.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
import java.util.Timer;
import java.util.TimerTask;

import android.Manifest;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.AlertDialog;
Expand All @@ -15,32 +14,26 @@
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.net.ConnectivityManager;
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;
import android.provider.Settings;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.CheckBox;
import android.widget.TextView;
import android.widget.Toast;

import androidx.annotation.NonNull;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;

import static android.provider.Settings.ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS;

public class I2PDActivity extends Activity {
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:";

Expand Down Expand Up @@ -121,26 +114,30 @@ public void onCreate(Bundle savedInstanceState) {

if (daemon == null) {
ConnectivityManager connectivityManager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
daemon = new DaemonWrapper(getAssets(), connectivityManager);
daemon = new DaemonWrapper(this, getAssets(), connectivityManager);
}
ForegroundService.init(daemon);

daemon.addStateChangeListener(daemonStateUpdatedListener);
daemonStateUpdatedListener.daemonStateUpdate(DaemonWrapper.State.uninitialized, daemon.getState());

// request permissions
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
if(!Environment.isExternalStorageManager()) {
Log.e(TAG, "MANAGE_EXTERNAL_STORAGE perm declined, stopping i2pd");
i2pdStop();
// Service checkboxes are status indicators; tap opens i2pd settings (e.g. enable SAM).
View.OnClickListener openI2pdSettings = v -> {
// Restore live status — CheckBox would otherwise toggle on click.
if (daemon != null && daemon.isStartedOkay()) {
HTTPProxyState.setChecked(I2PD_JNI.getHTTPProxyState());
SOCKSProxyState.setChecked(I2PD_JNI.getSOCKSProxyState());
BOBState.setChecked(I2PD_JNI.getBOBState());
SAMState.setChecked(I2PD_JNI.getSAMState());
I2CPState.setChecked(I2PD_JNI.getI2CPState());
}
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this,
new String[] { Manifest.permission.WRITE_EXTERNAL_STORAGE },
MY_PERMISSION_REQUEST_WRITE_EXTERNAL_STORAGE);
}
}
startActivity(new Intent(getApplicationContext(), MainPreferenceActivity.class));
};
HTTPProxyState.setOnClickListener(openI2pdSettings);
SOCKSProxyState.setOnClickListener(openI2pdSettings);
BOBState.setOnClickListener(openI2pdSettings);
SAMState.setOnClickListener(openI2pdSettings);
I2CPState.setOnClickListener(openI2pdSettings);

doBindService();

Expand Down Expand Up @@ -175,19 +172,6 @@ protected void onDestroy() {
}
}

@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults)
{
if (requestCode == MY_PERMISSION_REQUEST_WRITE_EXTERNAL_STORAGE) {
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED)
Log.w(TAG, "WR_EXT_STORAGE perm granted");
else {
Log.e(TAG, "WR_EXT_STORAGE perm declined, stopping i2pd");
i2pdStop();
}
}
}

private void cancelGracefulStop0() {
Timer gracefulQuitTimer = getGracefulQuitTimer();
if (gracefulQuitTimer != null) {
Expand Down Expand Up @@ -270,7 +254,7 @@ private boolean isBatteryOptimizationsOpenOsDialogApiAvailable() {
}

@Override
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
Expand Down Expand Up @@ -305,6 +289,10 @@ public boolean onOptionsItemSelected(@NonNull MenuItem item) {
Toast.makeText(this,"I2Pd not was started!", Toast.LENGTH_SHORT).show();
return true;
case R.id.action_settings:
// i2pd.conf (HTTP/SOCKS/SAM/etc.) — primary settings users need
startActivity(new Intent(getApplicationContext(), MainPreferenceActivity.class));
return true;
case R.id.action_app_settings:
startActivity(new Intent(getApplicationContext(), SettingsActivity.class));
return true;
}
Expand Down
Loading