Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix #50 and fix all subsequent issues with the fix. Remove JNI #92

Draft
wants to merge 11 commits into
base: no-jni
Choose a base branch
from
4 changes: 2 additions & 2 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,11 @@ android {
disable 'DuplicatePlatformClasses'
}

compileSdkVersion 33
compileSdkVersion 34

defaultConfig {
applicationId "org.purplei2p.i2pd"
targetSdkVersion 33
targetSdkVersion 34
// TODO: 24?
minSdkVersion 16
versionCode 2530000
Expand Down
4 changes: 2 additions & 2 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:installLocation="auto">

<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="29" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"
Expand All @@ -25,7 +25,7 @@
android:usesCleartextTraffic="true">

<service
android:name=".I2PdQSTileService"
android:name=".I2pdQuickSettingsTileService"
android:exported="true"
android:label="I2Pd"
android:icon="@drawable/ic_logo"
Expand Down
7 changes: 4 additions & 3 deletions app/src/main/java/org/purplei2p/i2pd/DaemonWrapper.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@

import androidx.annotation.RequiresApi;

import org.purplei2p.i2pd.appscope.App;

public class DaemonWrapper {

private static final String TAG = "i2pd";
Expand Down Expand Up @@ -118,11 +120,11 @@ public State getState() {
return state;
}

public DaemonWrapper(Context ctx, AssetManager assetManager, ConnectivityManager connectivityManager){
public DaemonWrapper(App app, Context ctx, AssetManager assetManager, ConnectivityManager connectivityManager){
this.assetManager = assetManager;
this.connectivityManager = connectivityManager;
setState(State.starting);
//startDaemon(ctx); //need to start when storage permissions to the datadir exist
if(app.isPermittedToWriteToExternalStorage())startDaemonIfStopped(ctx);
}

private Throwable lastThrowable;
Expand Down Expand Up @@ -257,7 +259,6 @@ private void processAssets() {
copyAsset("certificates");
copyAsset("tunnels.d");
copyAsset("i2pd.conf");
copyAsset("subscriptions.txt");
copyAsset("tunnels.conf");

// update holder file about successful copying
Expand Down
86 changes: 54 additions & 32 deletions app/src/main/java/org/purplei2p/i2pd/ForegroundService.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@
import androidx.core.app.NotificationCompat;
import android.util.Log;

import org.purplei2p.i2pd.appscope.App;
import org.purplei2p.i2pd.receivers.BootUpReceiver;

public class ForegroundService extends Service {
private static final String TAG = "FgService";
private volatile boolean shown;
Expand All @@ -24,46 +27,45 @@ public static ForegroundService getInstance() {
return instance;
}

private static ForegroundService instance;
private static volatile ForegroundService instance;
private static volatile DaemonWrapper daemon;
private static final Object initDeinitLock = new Object();

private final DaemonWrapper.StateUpdateListener daemonStateUpdatedListener =
new DaemonWrapper.StateUpdateListener() {

@Override
public void daemonStateUpdate(DaemonWrapper.State oldValue, DaemonWrapper.State newValue) {
updateNotificationText();
}
};
(oldValue, newValue) -> updateNotificationText();

private void updateNotificationText() {
Log.d(TAG, "FgSvc.updateNotificationText() enter");
try {
synchronized (initDeinitLock) {
if (shown) cancelNotification();
showNotification();
if (shown){
Log.d(TAG, "FgSvc.updateNotificationText() calling cancelNotification()");
cancelNotification();
}
if(App.isStartDaemon()){
Log.d(TAG, "FgSvc.updateNotificationText() calling showNotification()");
showNotification();
}
}
} catch (Throwable tr) {
Log.e(TAG,"error ignored",tr);
Log.e(TAG,"error ignored", tr);
}
Log.d(TAG, "FgSvc.updateNotificationText() leave");
}


private NotificationManager notificationManager;

// Unique Identification Number for the Notification.
// We use it on Notification start, and to cancel it.
private static final int NOTIFICATION = 1;
private static final int NOTIFICATION_ID = 1;

/**
* Class for clients to access. Because we know this service always
* runs in the same process as its clients, we don't need to deal with
* IPC.
*/
public class LocalBinder extends Binder {
ForegroundService getService() {
return ForegroundService.this;
}
}

public static void init(DaemonWrapper daemon) {
Expand All @@ -79,31 +81,51 @@ private static void initCheck() {

@Override
public void onCreate() {
Log.d(TAG, "FgSvc.onCreate()");
notificationManager = (NotificationManager)getSystemService(NOTIFICATION_SERVICE);
final App app = (App) getApplication();
if (daemon == null) daemon = App.getDaemonWrapper();
if (daemon == null) {
if(App.isStartDaemon()) {
Log.d(TAG, "FgSvc.onCreate() calling app.createDaemonWrapper()");
app.createDaemonWrapper();
}
daemon = App.getDaemonWrapper();
}
instance = this;
initCheck();
Log.d(TAG, "FgSvc.onCreate() leave");
}

private void setListener() {
daemon.addStateChangeListener(daemonStateUpdatedListener);
final DaemonWrapper dw = daemon;
if (dw != null) dw.addStateChangeListener(daemonStateUpdatedListener);
updateNotificationText();
}

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d("ForegroundService", "Received start id " + startId + ": " + intent);
Log.d(TAG, "Received start id " + startId + ": " + intent);
readFlags(flags);
return START_STICKY;
}

private void readFlags(int flags) {
if ((flags&START_FLAG_REDELIVERY) == START_FLAG_REDELIVERY)
Log.d(TAG,"START_FLAG_REDELIVERY");
if ((flags&START_FLAG_RETRY) == START_FLAG_RETRY)
Log.d(TAG,"START_FLAG_RETRY");
}
@Override
public void onDestroy() {
stop();
}

public void stop() {
Log.e(TAG,"stop() enter", new Throwable("dumpstack"));
cancelNotification();
deinitCheck();
instance = null;
Log.d(TAG,"stop() leave");
}

public static void deinit() {
Expand All @@ -118,14 +140,10 @@ private static void deinitCheck() {
}

private void cancelNotification() {
Log.d(TAG, "FgSvc.cancelNotification()");
synchronized (initDeinitLock) {
// Cancel the persistent notification.
notificationManager.cancel(NOTIFICATION);

notificationManager.cancel(NOTIFICATION_ID);
stopForeground(true);

// Tell the user we stopped.
//Toast.makeText(this, R.string.i2pd_service_stopped, Toast.LENGTH_SHORT).show();
shown = false;
}
}
Expand All @@ -143,25 +161,27 @@ public IBinder onBind(Intent intent) {
* Show a notification while this service is running.
*/
private void showNotification() {
Log.d(TAG, "FgSvc.showNotification() enter");
synchronized (initDeinitLock) {
Log.d(TAG, "FgSvc.showNotification(): daemon="+daemon);
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());
Log.d(TAG, "FgSvc.showNotification(): text="+text);

// The PendingIntent to launch our activity if the user selects this notification
PendingIntent contentIntent = PendingIntent.getActivity(this, 0,
new Intent(this, I2PDActivity.class),
new Intent(this, I2PDPermsAskerActivity.class),
Build.VERSION.SDK_INT >= Build.VERSION_CODES.M ? FLAG_IMMUTABLE : 0);

// If earlier version channel ID is not used
// on old Android, the 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 >= Build.VERSION_CODES.O ? 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.ic_notification_icon); // the status icon
builder = builder.setPriority(Notification.PRIORITY_DEFAULT);
.setSmallIcon(R.drawable.ic_notification_icon) // the status icon
.setPriority(Notification.PRIORITY_DEFAULT);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
builder = builder.setCategory(Notification.CATEGORY_SERVICE);
Notification notification = builder
Expand All @@ -174,22 +194,24 @@ private void showNotification() {

// Send the notification.
//mNM.notify(NOTIFICATION, notification);
startForeground(NOTIFICATION, notification);
Log.d(TAG, "FgSvc.showNotification(): calling startForeground()");
startForeground(NOTIFICATION_ID, notification);
shown = true;
}
}
Log.d(TAG, "FgSvc.showNotification() leave");
}

@RequiresApi(Build.VERSION_CODES.O)
private synchronized String createNotificationChannel() {
String channelId = getString(R.string.app_name);
CharSequence channelName = "I2Pd service";
CharSequence channelName = getString(R.string.i2pd_service);
NotificationChannel chan = new NotificationChannel(channelId, channelName, NotificationManager.IMPORTANCE_LOW);
//chan.setLightColor(Color.PURPLE);
chan.setLockscreenVisibility(Notification.VISIBILITY_PRIVATE);
NotificationManager service = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
if (service != null) service.createNotificationChannel(chan);
else Log.e(TAG, "error: NOTIFICATION_SERVICE is null");
else Log.e(TAG, "error: NOTIFICATION_SERVICE is null, haven't called createNotificationChannel");
return channelId;
}
}
77 changes: 4 additions & 73 deletions app/src/main/java/org/purplei2p/i2pd/I2PDActivity.java
Original file line number Diff line number Diff line change
Expand Up @@ -117,23 +117,18 @@ public void onCreate(Bundle savedInstanceState) {
Log.d(TAG, "onCreate");
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
startService(new Intent(this, ForegroundService.class));
textView = (TextView) findViewById(R.id.appStatusText);
textView = findViewById(R.id.appStatusText);
/*
HTTPProxyState = (CheckBox) findViewById(R.id.service_httpproxy_box);
SOCKSProxyState = (CheckBox) findViewById(R.id.service_socksproxy_box);
BOBState = (CheckBox) findViewById(R.id.service_bob_box);
SAMState = (CheckBox) findViewById(R.id.service_sam_box);
I2CPState = (CheckBox) findViewById(R.id.service_i2cp_box);*/

/*if (getDaemon() == null) {
ConnectivityManager connectivityManager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
getDaemon() = new getDaemon()Wrapper(getAssets(), connectivityManager);
App.setStartDaemon(true);
if (getDaemon() == null) {
((App)getApplication()).createDaemonWrapper();
}
ForegroundService.init(getDaemon());

*/
//getDaemon()StateUpdatedListener.getDaemon()StateUpdate(getDaemon()Wrapper.State.uninitialized, App.getgetDaemon()Wrapper().getState());

// request permissions
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
Expand All @@ -154,7 +149,6 @@ public void onCreate(Bundle savedInstanceState) {
getDaemon().startDaemonIfStopped(getApplicationContext());
getDaemon().addStateChangeListener(daemonStateUpdatedListener);
updateStatusText();
doBindService();

final Timer gracefulQuitTimer = getGracefulQuitTimer();
if (gracefulQuitTimer != null) {
Expand All @@ -172,19 +166,8 @@ public void onCreate(Bundle savedInstanceState) {
protected void onDestroy() {
super.onDestroy();
textView = null;
ForegroundService.deinit();
getDaemon().removeStateChangeListener(daemonStateUpdatedListener);
//cancelGracefulStop0();
try {
doUnbindService();
} catch (IllegalArgumentException ex) {
Log.e(TAG, "throwable caught and ignored", ex);
if (ex.getMessage().startsWith("Service not registered: " + org.purplei2p.i2pd.I2PDActivity.class.getName())) {
Log.i(TAG, "Service not registered exception seems to be normal, not a bug it seems.");
}
} catch (Throwable tr) {
Log.e(TAG, "throwable caught and ignored", tr);
}
}

@Override
Expand Down Expand Up @@ -216,58 +199,6 @@ private CharSequence throwableToString(Throwable tr) {
return sw.toString();
}

// private LocalService mBoundService;

private ServiceConnection mConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service) {
/* This is called when the connection with the service has been
established, giving us the service object we can use to
interact with the service. Because we have bound to a explicit
service that we know is running in our own process, we can
cast its IBinder to a concrete class and directly access it. */
// mBoundService = ((LocalService.LocalBinder)service).getService();

/* Tell the user about this for our demo. */
// Toast.makeText(Binding.this, R.string.local_service_connected,
// Toast.LENGTH_SHORT).show();
}

public void onServiceDisconnected(ComponentName className) {
/* This is called when the connection with the service has been
unexpectedly disconnected -- that is, its process crashed.
Because it is running in our same process, we should never
see this happen. */
// mBoundService = null;
// Toast.makeText(Binding.this, R.string.local_service_disconnected,
// Toast.LENGTH_SHORT).show();
}
};

private static volatile boolean mIsBound;

private void doBindService() {
synchronized (I2PDActivity.class) {
if (mIsBound)
return;
// Establish a connection with the service. We use an explicit
// class name because we want a specific service implementation that
// we know will be running in our own process (and thus won't be
// supporting component replacement by other applications).
bindService(new Intent(this, ForegroundService.class), mConnection, Context.BIND_AUTO_CREATE);
mIsBound = true;
}
}

private void doUnbindService() {
synchronized (I2PDActivity.class) {
if (mIsBound) {
// Detach our existing connection.
unbindService(mConnection);
mIsBound = false;
}
}
}

@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
Expand Down
Loading