From db3e48a81a87c8ce70e727f815c011c7a3f62d52 Mon Sep 17 00:00:00 2001 From: user Date: Sat, 24 Oct 2020 03:52:53 +0800 Subject: [PATCH] android: more logical daemon state changes --- .../src/org/purplei2p/i2pd/DaemonWrapper.java | 41 +++--- .../org/purplei2p/i2pd/ForegroundService.java | 119 ++++++++++-------- .../src/org/purplei2p/i2pd/I2PDActivity.java | 53 ++++---- 3 files changed, 108 insertions(+), 105 deletions(-) diff --git a/android/src/org/purplei2p/i2pd/DaemonWrapper.java b/android/src/org/purplei2p/i2pd/DaemonWrapper.java index 7f9fcd93..414a3ed1 100644 --- a/android/src/org/purplei2p/i2pd/DaemonWrapper.java +++ b/android/src/org/purplei2p/i2pd/DaemonWrapper.java @@ -12,7 +12,6 @@ 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; @@ -32,7 +31,7 @@ public class DaemonWrapper { private boolean assetsCopied; public interface StateUpdateListener { - void daemonStateUpdate(); + void daemonStateUpdate(State oldValue, State newValue); } private final Set stateUpdateListeners = new HashSet<>(); @@ -58,7 +57,7 @@ public class DaemonWrapper { return; state = newState; - fireStateUpdate1(); + fireStateUpdate1(oldState, newState); } public synchronized void stopAcceptingTunnels() { @@ -81,12 +80,10 @@ public class DaemonWrapper { } } - public synchronized int GetTransitTunnelsCount() { + public int getTransitTunnelsCount() { return I2PD_JNI.GetTransitTunnelsCount(); } - private volatile boolean startedOkay; - public enum State { uninitialized(R.string.uninitialized), starting(R.string.starting), @@ -105,7 +102,11 @@ public class DaemonWrapper { public int getStatusStringResourceId() { return statusStringResourceId; } - }; + + public boolean isStartedOkay() { + return equals(State.startedOkay) || equals(State.gracefulShutdownInProgress); + } + } private volatile State state = State.uninitialized; @@ -134,7 +135,6 @@ public class DaemonWrapper { daemonStartResult = I2PD_JNI.startDaemon(); if ("ok".equals(daemonStartResult)) { setState(State.startedOkay); - setStartedOkay(true); } else setState(State.startFailed); } @@ -148,11 +148,11 @@ public class DaemonWrapper { private Throwable lastThrowable; private String daemonStartResult = "N/A"; - private void fireStateUpdate1() { + private void fireStateUpdate1(State oldValue, State newValue) { Log.i(TAG, "daemon state change: " + state); for (StateUpdateListener listener : stateUpdateListeners) { try { - listener.daemonStateUpdate(); + listener.daemonStateUpdate(oldValue, newValue); } catch (Throwable tr) { Log.e(TAG, "exception in listener ignored", tr); } @@ -167,18 +167,8 @@ public class DaemonWrapper { 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; - } + return getState().isStartedOkay(); } public synchronized void stopDaemon() { @@ -189,7 +179,6 @@ public class DaemonWrapper { Log.e(TAG, "", tr); } - setStartedOkay(false); setState(State.stopped); } } @@ -197,7 +186,7 @@ public class DaemonWrapper { private void processAssets() { if (!assetsCopied) { try { - assetsCopied = true; // prevent from running on every state update + assetsCopied = true; File holderFile = new File(i2pdpath, "assets.ready"); String versionName = BuildConfig.VERSION_NAME; // here will be app version, like 2.XX.XX @@ -283,12 +272,10 @@ public class DaemonWrapper { * 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); + 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 @@ -355,7 +342,7 @@ public class DaemonWrapper { Log.e(TAG, "fileOrDirectory.delete() returned " + deleteResult + ", absolute path='" + fileOrDirectory.getAbsolutePath() + "'"); } - public void registerNetworkCallback(){ + private void registerNetworkCallback(){ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) registerNetworkCallback0(); } diff --git a/android/src/org/purplei2p/i2pd/ForegroundService.java b/android/src/org/purplei2p/i2pd/ForegroundService.java index c1c918ac..c97b7f1f 100644 --- a/android/src/org/purplei2p/i2pd/ForegroundService.java +++ b/android/src/org/purplei2p/i2pd/ForegroundService.java @@ -23,22 +23,28 @@ public class ForegroundService extends Service { private static volatile DaemonWrapper daemon; + private static final Object initDeinitLock = new Object(); + private final DaemonWrapper.StateUpdateListener daemonStateUpdatedListener = new DaemonWrapper.StateUpdateListener() { @Override - public void daemonStateUpdate() { - try { - synchronized (ForegroundService.this) { - if (shown) cancelNotification(); - showNotification(); - } - } catch (Throwable tr) { - Log.e(TAG,"error ignored",tr); - } + public void daemonStateUpdate(DaemonWrapper.State oldValue, DaemonWrapper.State newValue) { + updateNotificationText(); } }; + private void updateNotificationText() { + try { + synchronized (initDeinitLock) { + if (shown) cancelNotification(); + showNotification(); + } + } catch (Throwable tr) { + Log.e(TAG,"error ignored",tr); + } + } + private NotificationManager notificationManager; @@ -63,7 +69,9 @@ public class ForegroundService extends Service { } private static void initCheck() { - if(instance!=null && daemon!=null) instance.setListener(); + synchronized (initDeinitLock) { + if (instance != null && daemon != null) instance.setListener(); + } } @Override @@ -75,7 +83,7 @@ public class ForegroundService extends Service { private void setListener() { daemon.addStateChangeListener(daemonStateUpdatedListener); - if (!shown) daemonStateUpdatedListener.daemonStateUpdate(); + updateNotificationText(); } @Override @@ -96,18 +104,23 @@ public class ForegroundService extends Service { } private static void deinitCheck() { - if(daemon!=null && instance!=null)daemon.removeStateChangeListener(instance.daemonStateUpdatedListener); + synchronized (initDeinitLock) { + if (daemon != null && instance != null) + daemon.removeStateChangeListener(instance.daemonStateUpdatedListener); + } } - private synchronized void cancelNotification() { - // Cancel the persistent notification. - notificationManager.cancel(NOTIFICATION); + private void cancelNotification() { + synchronized (initDeinitLock) { + // Cancel the persistent notification. + notificationManager.cancel(NOTIFICATION); - stopForeground(true); + stopForeground(true); - // Tell the user we stopped. - //Toast.makeText(this, R.string.i2pd_service_stopped, Toast.LENGTH_SHORT).show(); - shown=false; + // Tell the user we stopped. + //Toast.makeText(this, R.string.i2pd_service_stopped, Toast.LENGTH_SHORT).show(); + shown = false; + } } @Override @@ -122,39 +135,41 @@ public class ForegroundService extends Service { /** * Show a notification while this service is running. */ - private synchronized void showNotification() { - 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); - - // 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(); - - // Send the notification. - //mNM.notify(NOTIFICATION, notification); - startForeground(NOTIFICATION, notification); - shown = true; + private void showNotification() { + synchronized (initDeinitLock) { + 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); + + // 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(); + + // Send the notification. + //mNM.notify(NOTIFICATION, notification); + startForeground(NOTIFICATION, notification); + shown = true; + } } } diff --git a/android/src/org/purplei2p/i2pd/I2PDActivity.java b/android/src/org/purplei2p/i2pd/I2PDActivity.java index 0801a655..97870e63 100755 --- a/android/src/org/purplei2p/i2pd/I2PDActivity.java +++ b/android/src/org/purplei2p/i2pd/I2PDActivity.java @@ -7,7 +7,6 @@ import java.util.TimerTask; import android.Manifest; import android.annotation.SuppressLint; -import android.annotation.TargetApi; import android.app.Activity; import android.app.AlertDialog; import android.content.ActivityNotFoundException; @@ -18,9 +17,6 @@ import android.content.ServiceConnection; import android.content.SharedPreferences; import android.content.pm.PackageManager; import android.net.ConnectivityManager; -import android.net.Network; -import android.net.NetworkCapabilities; -import android.net.NetworkRequest; import android.net.Uri; import android.os.Bundle; import android.os.Build; @@ -36,7 +32,6 @@ import android.widget.Toast; import androidx.annotation.NonNull; -import androidx.annotation.RequiresApi; import androidx.core.app.ActivityCompat; import androidx.core.content.ContextCompat; @@ -62,26 +57,31 @@ public class I2PDActivity extends Activity { private final DaemonWrapper.StateUpdateListener daemonStateUpdatedListener = new DaemonWrapper.StateUpdateListener() { @Override - public void daemonStateUpdate() { - runOnUiThread(() -> { - try { - if (textView == null) - return; - Throwable tr = daemon.getLastThrowable(); - if (tr!=null) { - textView.setText(throwableToString(tr)); - return; - } - 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); - } - }); + public void daemonStateUpdate(DaemonWrapper.State oldValue, DaemonWrapper.State newValue) { + updateStatusText(); } }; + + private void updateStatusText() { + runOnUiThread(() -> { + try { + if (textView == null) + return; + Throwable tr = daemon.getLastThrowable(); + if (tr!=null) { + textView.setText(throwableToString(tr)); + return; + } + 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); + } + }); + } + private static volatile long graceStartedMillis; private static final Object graceStartedMillis_LOCK = new Object(); private Menu optionsMenu; @@ -110,7 +110,7 @@ public class I2PDActivity extends Activity { textView = new TextView(this); setContentView(textView); daemon.addStateChangeListener(daemonStateUpdatedListener); - daemonStateUpdatedListener.daemonStateUpdate(); + daemonStateUpdatedListener.daemonStateUpdate(DaemonWrapper.State.uninitialized, daemon.getState()); // request permissions if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { @@ -372,9 +372,10 @@ public class I2PDActivity extends Activity { if (gracefulQuitTimerOld != null) gracefulQuitTimerOld.cancel(); - if(daemon.GetTransitTunnelsCount() <= 0) { // no tunnels left + if(daemon.getTransitTunnelsCount() <= 0) { // no tunnels left Log.d(TAG, "no transit tunnels left, stopping"); i2pdStop(); + return; } final Timer gracefulQuitTimer = new Timer(true); @@ -390,7 +391,7 @@ public class I2PDActivity extends Activity { final TimerTask tickerTask = new TimerTask() { @Override public void run() { - daemonStateUpdatedListener.daemonStateUpdate(); + updateStatusText(); } }; gracefulQuitTimer.scheduleAtFixedRate(tickerTask, 0/*start delay*/, 1000/*millis period*/);