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:
parent
d9b87e877d
commit
db3e48a81a
@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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*/);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user