1
0
mirror of https://github.com/PurpleI2P/i2pd.git synced 2025-03-10 09:21:08 +00:00

android: more logical daemon state changes

This commit is contained in:
user 2020-10-24 03:52:53 +08:00
parent d9b87e877d
commit db3e48a81a
3 changed files with 104 additions and 101 deletions

View File

@ -12,7 +12,6 @@ import java.util.HashSet;
import java.util.Set; import java.util.Set;
import android.annotation.TargetApi; import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.AssetManager; import android.content.res.AssetManager;
import android.net.ConnectivityManager; import android.net.ConnectivityManager;
import android.net.Network; import android.net.Network;
@ -32,7 +31,7 @@ public class DaemonWrapper {
private boolean assetsCopied; private boolean assetsCopied;
public interface StateUpdateListener { public interface StateUpdateListener {
void daemonStateUpdate(); void daemonStateUpdate(State oldValue, State newValue);
} }
private final Set<StateUpdateListener> stateUpdateListeners = new HashSet<>(); private final Set<StateUpdateListener> stateUpdateListeners = new HashSet<>();
@ -58,7 +57,7 @@ public class DaemonWrapper {
return; return;
state = newState; state = newState;
fireStateUpdate1(); fireStateUpdate1(oldState, newState);
} }
public synchronized void stopAcceptingTunnels() { public synchronized void stopAcceptingTunnels() {
@ -81,12 +80,10 @@ public class DaemonWrapper {
} }
} }
public synchronized int GetTransitTunnelsCount() { public int getTransitTunnelsCount() {
return I2PD_JNI.GetTransitTunnelsCount(); return I2PD_JNI.GetTransitTunnelsCount();
} }
private volatile boolean startedOkay;
public enum State { public enum State {
uninitialized(R.string.uninitialized), uninitialized(R.string.uninitialized),
starting(R.string.starting), starting(R.string.starting),
@ -105,7 +102,11 @@ public class DaemonWrapper {
public int getStatusStringResourceId() { public int getStatusStringResourceId() {
return statusStringResourceId; return statusStringResourceId;
} }
};
public boolean isStartedOkay() {
return equals(State.startedOkay) || equals(State.gracefulShutdownInProgress);
}
}
private volatile State state = State.uninitialized; private volatile State state = State.uninitialized;
@ -134,7 +135,6 @@ public class DaemonWrapper {
daemonStartResult = I2PD_JNI.startDaemon(); daemonStartResult = I2PD_JNI.startDaemon();
if ("ok".equals(daemonStartResult)) { if ("ok".equals(daemonStartResult)) {
setState(State.startedOkay); setState(State.startedOkay);
setStartedOkay(true);
} else } else
setState(State.startFailed); setState(State.startFailed);
} }
@ -148,11 +148,11 @@ public class DaemonWrapper {
private Throwable lastThrowable; private Throwable lastThrowable;
private String daemonStartResult = "N/A"; private String daemonStartResult = "N/A";
private void fireStateUpdate1() { private void fireStateUpdate1(State oldValue, State newValue) {
Log.i(TAG, "daemon state change: " + state); Log.i(TAG, "daemon state change: " + state);
for (StateUpdateListener listener : stateUpdateListeners) { for (StateUpdateListener listener : stateUpdateListeners) {
try { try {
listener.daemonStateUpdate(); listener.daemonStateUpdate(oldValue, newValue);
} catch (Throwable tr) { } catch (Throwable tr) {
Log.e(TAG, "exception in listener ignored", tr); Log.e(TAG, "exception in listener ignored", tr);
} }
@ -167,18 +167,8 @@ public class DaemonWrapper {
return daemonStartResult; return daemonStartResult;
} }
private final Object startedOkayLock = new Object();
public boolean isStartedOkay() { public boolean isStartedOkay() {
synchronized (startedOkayLock) { return getState().isStartedOkay();
return startedOkay;
}
}
private void setStartedOkay(boolean startedOkay) {
synchronized (startedOkayLock) {
this.startedOkay = startedOkay;
}
} }
public synchronized void stopDaemon() { public synchronized void stopDaemon() {
@ -189,7 +179,6 @@ public class DaemonWrapper {
Log.e(TAG, "", tr); Log.e(TAG, "", tr);
} }
setStartedOkay(false);
setState(State.stopped); setState(State.stopped);
} }
} }
@ -197,7 +186,7 @@ public class DaemonWrapper {
private void processAssets() { private void processAssets() {
if (!assetsCopied) { if (!assetsCopied) {
try { try {
assetsCopied = true; // prevent from running on every state update assetsCopied = true;
File holderFile = new File(i2pdpath, "assets.ready"); File holderFile = new File(i2pdpath, "assets.ready");
String versionName = BuildConfig.VERSION_NAME; // here will be app version, like 2.XX.XX 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. * Path to asset, relative to app's assets directory.
*/ */
private void copyAsset(String path) { private void copyAsset(String path) {
AssetManager manager = assetManager;
// If we have a directory, we make it and recurse. If a file, we copy its // If we have a directory, we make it and recurse. If a file, we copy its
// contents. // contents.
try { try {
String[] contents = manager.list(path); String[] contents = assetManager.list(path);
// The documentation suggests that list throws an IOException, but doesn't // 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 // 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() + "'"); 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(); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) registerNetworkCallback0();
} }

View File

@ -23,22 +23,28 @@ public class ForegroundService extends Service {
private static volatile DaemonWrapper daemon; private static volatile DaemonWrapper daemon;
private static final Object initDeinitLock = new Object();
private final DaemonWrapper.StateUpdateListener daemonStateUpdatedListener = private final DaemonWrapper.StateUpdateListener daemonStateUpdatedListener =
new DaemonWrapper.StateUpdateListener() { new DaemonWrapper.StateUpdateListener() {
@Override @Override
public void daemonStateUpdate() { public void daemonStateUpdate(DaemonWrapper.State oldValue, DaemonWrapper.State newValue) {
try { updateNotificationText();
synchronized (ForegroundService.this) {
if (shown) cancelNotification();
showNotification();
}
} catch (Throwable tr) {
Log.e(TAG,"error ignored",tr);
}
} }
}; };
private void updateNotificationText() {
try {
synchronized (initDeinitLock) {
if (shown) cancelNotification();
showNotification();
}
} catch (Throwable tr) {
Log.e(TAG,"error ignored",tr);
}
}
private NotificationManager notificationManager; private NotificationManager notificationManager;
@ -63,7 +69,9 @@ public class ForegroundService extends Service {
} }
private static void initCheck() { private static void initCheck() {
if(instance!=null && daemon!=null) instance.setListener(); synchronized (initDeinitLock) {
if (instance != null && daemon != null) instance.setListener();
}
} }
@Override @Override
@ -75,7 +83,7 @@ public class ForegroundService extends Service {
private void setListener() { private void setListener() {
daemon.addStateChangeListener(daemonStateUpdatedListener); daemon.addStateChangeListener(daemonStateUpdatedListener);
if (!shown) daemonStateUpdatedListener.daemonStateUpdate(); updateNotificationText();
} }
@Override @Override
@ -96,18 +104,23 @@ public class ForegroundService extends Service {
} }
private static void deinitCheck() { 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() { private void cancelNotification() {
// Cancel the persistent notification. synchronized (initDeinitLock) {
notificationManager.cancel(NOTIFICATION); // Cancel the persistent notification.
notificationManager.cancel(NOTIFICATION);
stopForeground(true); stopForeground(true);
// Tell the user we stopped. // Tell the user we stopped.
//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
@ -122,39 +135,41 @@ 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() {
if(daemon!=null) { synchronized (initDeinitLock) {
// In this sample, we'll use the same text for the ticker and the expanded notification if (daemon != null) {
CharSequence text = getText(daemon.getState().getStatusStringResourceId()); // 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 // The PendingIntent to launch our activity if the user selects this notification
PendingIntent contentIntent = PendingIntent.getActivity(this, 0, PendingIntent contentIntent = PendingIntent.getActivity(this, 0,
new Intent(this, I2PDActivity.class), 0); new Intent(this, I2PDActivity.class), 0);
// If earlier version channel ID is not used // 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) // 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() : ""; String channelId = Build.VERSION.SDK_INT >= 26 ? createNotificationChannel() : "";
// Set the info for the views that show in the notification panel. // Set the info for the views that show in the notification panel.
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) if (Build.VERSION.SDK_INT >= 16)
builder = builder.setPriority(Notification.PRIORITY_DEFAULT); builder = builder.setPriority(Notification.PRIORITY_DEFAULT);
if (Build.VERSION.SDK_INT >= 21) if (Build.VERSION.SDK_INT >= 21)
builder = builder.setCategory(Notification.CATEGORY_SERVICE); 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
.setContentTitle(getText(R.string.app_name)) // the label of the entry .setContentTitle(getText(R.string.app_name)) // the label of the entry
.setContentText(text) // the contents of the entry .setContentText(text) // the contents of the entry
.setContentIntent(contentIntent) // The intent to send when the entry is clicked .setContentIntent(contentIntent) // The intent to send when the entry is clicked
.build(); .build();
// Send the notification. // Send the notification.
//mNM.notify(NOTIFICATION, notification); //mNM.notify(NOTIFICATION, notification);
startForeground(NOTIFICATION, notification); startForeground(NOTIFICATION, notification);
shown = true; shown = true;
}
} }
} }

View File

@ -7,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;
@ -18,9 +17,6 @@ import android.content.ServiceConnection;
import android.content.SharedPreferences; import android.content.SharedPreferences;
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;
@ -36,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;
@ -62,26 +57,31 @@ public class I2PDActivity extends Activity {
private final DaemonWrapper.StateUpdateListener daemonStateUpdatedListener = new DaemonWrapper.StateUpdateListener() { private final DaemonWrapper.StateUpdateListener daemonStateUpdatedListener = new DaemonWrapper.StateUpdateListener() {
@Override @Override
public void daemonStateUpdate() { public void daemonStateUpdate(DaemonWrapper.State oldValue, DaemonWrapper.State newValue) {
runOnUiThread(() -> { updateStatusText();
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 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 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;
@ -110,7 +110,7 @@ public class I2PDActivity extends Activity {
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) {
@ -372,9 +372,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);
@ -390,7 +391,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*/);