diff --git a/app/src/main/java/com/github/gotify/service/WebSocketConnection.java b/app/src/main/java/com/github/gotify/service/WebSocketConnection.java
index feea22aa..28101283 100644
--- a/app/src/main/java/com/github/gotify/service/WebSocketConnection.java
+++ b/app/src/main/java/com/github/gotify/service/WebSocketConnection.java
@@ -13,6 +13,7 @@
import com.github.gotify.log.Log;
import java.util.Calendar;
import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicLong;
import okhttp3.HttpUrl;
import okhttp3.OkHttpClient;
import okhttp3.Request;
@@ -21,6 +22,7 @@
import okhttp3.WebSocketListener;
class WebSocketConnection {
+ private static final AtomicLong ID = new AtomicLong(0);
private final ConnectivityManager connectivityManager;
private final AlarmManager alarmManager;
private OkHttpClient client;
@@ -38,7 +40,7 @@ class WebSocketConnection {
private BadRequestRunnable onBadRequest;
private OnNetworkFailureRunnable onNetworkFailure;
private Runnable onReconnected;
- private boolean isClosed;
+ private State state;
private Runnable onDisconnect;
WebSocketConnection(
@@ -108,24 +110,33 @@ private Request request() {
}
public synchronized WebSocketConnection start() {
+ if (state == State.Connecting || state == State.Connected) {
+ return this;
+ }
close();
- isClosed = false;
- Log.i("WebSocket: starting...");
+ state = State.Connecting;
+ long nextId = ID.incrementAndGet();
+ Log.i("WebSocket(" + nextId + "): starting...");
- webSocket = client.newWebSocket(request(), new Listener());
+ webSocket = client.newWebSocket(request(), new Listener(nextId));
return this;
}
public synchronized void close() {
if (webSocket != null) {
- Log.i("WebSocket: closing existing connection.");
- isClosed = true;
+ Log.i("WebSocket(" + ID.get() + "): closing existing connection.");
+ state = State.Disconnected;
webSocket.close(1000, "");
webSocket = null;
}
}
public synchronized void scheduleReconnect(long seconds) {
+ if (state == State.Connecting || state == State.Connected) {
+ return;
+ }
+ state = State.Scheduled;
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
Log.i(
"WebSocket: scheduling a restart in "
@@ -147,39 +158,49 @@ public synchronized void scheduleReconnect(long seconds) {
}
private class Listener extends WebSocketListener {
+ private final long id;
+
+ public Listener(long id) {
+ this.id = id;
+ }
@Override
public void onOpen(WebSocket webSocket, Response response) {
- Log.i("WebSocket: opened");
- synchronized (this) {
- onOpen.run();
-
- if (errorCount > 0) {
- onReconnected.run();
- errorCount = 0;
- }
- }
+ syncExec(
+ () -> {
+ state = State.Connected;
+ Log.i("WebSocket(" + id + "): opened");
+ onOpen.run();
+
+ if (errorCount > 0) {
+ onReconnected.run();
+ errorCount = 0;
+ }
+ });
super.onOpen(webSocket, response);
}
@Override
public void onMessage(WebSocket webSocket, String text) {
- Log.i("WebSocket: received message " + text);
- synchronized (this) {
- Message message = Utils.JSON.fromJson(text, Message.class);
- onMessage.onSuccess(message);
- }
+ syncExec(
+ () -> {
+ Log.i("WebSocket(" + id + "): received message " + text);
+ Message message = Utils.JSON.fromJson(text, Message.class);
+ onMessage.onSuccess(message);
+ });
super.onMessage(webSocket, text);
}
@Override
public void onClosed(WebSocket webSocket, int code, String reason) {
- synchronized (this) {
- if (!isClosed) {
- Log.w("WebSocket: closed");
- onClose.run();
- }
- }
+ syncExec(
+ () -> {
+ if (state == State.Connected) {
+ Log.w("WebSocket(" + id + "): closed");
+ onClose.run();
+ }
+ state = State.Disconnected;
+ });
super.onClosed(webSocket, code, reason);
}
@@ -188,30 +209,40 @@ public void onClosed(WebSocket webSocket, int code, String reason) {
public void onFailure(WebSocket webSocket, Throwable t, Response response) {
String code = response != null ? "StatusCode: " + response.code() : "";
String message = response != null ? response.message() : "";
- Log.e("WebSocket: failure " + code + " Message: " + message, t);
- synchronized (this) {
- if (response != null && response.code() >= 400 && response.code() <= 499) {
- onBadRequest.execute(message);
- close();
- return;
- }
+ Log.e("WebSocket(" + id + "): failure " + code + " Message: " + message, t);
+ syncExec(
+ () -> {
+ state = State.Disconnected;
+ if (response != null && response.code() >= 400 && response.code() <= 499) {
+ onBadRequest.execute(message);
+ close();
+ return;
+ }
+
+ errorCount++;
+
+ NetworkInfo network = connectivityManager.getActiveNetworkInfo();
+ if (network == null || !network.isConnected()) {
+ Log.i("WebSocket(" + id + "): Network not connected");
+ onDisconnect.run();
+ return;
+ }
+
+ int minutes = Math.min(errorCount * 2 - 1, 20);
+
+ onNetworkFailure.execute(minutes);
+ scheduleReconnect(TimeUnit.MINUTES.toSeconds(minutes));
+ });
- errorCount++;
+ super.onFailure(webSocket, t, response);
+ }
- NetworkInfo network = connectivityManager.getActiveNetworkInfo();
- if (network == null || !network.isConnected()) {
- Log.i("WebSocket: Network not connected");
- onDisconnect.run();
- return;
+ private void syncExec(Runnable runnable) {
+ synchronized (this) {
+ if (ID.get() == id) {
+ runnable.run();
}
-
- int minutes = Math.min(errorCount * 2 - 1, 20);
-
- onNetworkFailure.execute(minutes);
- scheduleReconnect(TimeUnit.MINUTES.toSeconds(minutes));
}
-
- super.onFailure(webSocket, t, response);
}
}
@@ -222,4 +253,11 @@ interface BadRequestRunnable {
interface OnNetworkFailureRunnable {
void execute(long millis);
}
+
+ enum State {
+ Scheduled,
+ Connecting,
+ Connected,
+ Disconnected
+ }
}
diff --git a/app/src/main/java/com/github/gotify/service/WebSocketService.java b/app/src/main/java/com/github/gotify/service/WebSocketService.java
index fbce4195..e772d7c8 100644
--- a/app/src/main/java/com/github/gotify/service/WebSocketService.java
+++ b/app/src/main/java/com/github/gotify/service/WebSocketService.java
@@ -35,6 +35,8 @@
import java.util.Map;
import java.util.concurrent.atomic.AtomicLong;
+import static com.github.gotify.api.Callback.call;
+
public class WebSocketService extends Service {
public static final String NEW_MESSAGE_BROADCAST =
@@ -105,7 +107,7 @@ private void startPushService() {
cm,
alarmManager)
.onOpen(this::onOpen)
- .onClose(() -> foreground(getString(R.string.websocket_closed)))
+ .onClose(this::onClose)
.onBadRequest(this::onBadRequest)
.onNetworkFailure(
(min) -> foreground(getString(R.string.websocket_failed, min)))
@@ -122,6 +124,24 @@ private void startPushService() {
picassoHandler.updateAppIds();
}
+ private void onClose() {
+ foreground(getString(R.string.websocket_closed_try_reconnect));
+ ClientFactory.userApiWithToken(settings)
+ .currentUser()
+ .enqueue(
+ call(
+ (ignored) -> this.doReconnect(),
+ (exception) -> {
+ if (exception.code() == 401) {
+ foreground(getString(R.string.websocket_closed_logout));
+ } else {
+ Log.i(
+ "WebSocket closed but the user still authenticated, trying to reconnect");
+ this.doReconnect();
+ }
+ }));
+ }
+
private void onDisconnect() {
foreground(getString(R.string.websocket_no_network));
}
@@ -131,7 +151,7 @@ private void doReconnect() {
return;
}
- connection.scheduleReconnect(5);
+ connection.scheduleReconnect(15);
}
private void onBadRequest(String message) {
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index a52569e7..39c28489 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -21,7 +21,8 @@
Server returned 401 unauthorized while trying to get current user. This can be caused by deleting the client for this session.
Could not get current user. Server (%s) responded with code %d: body (first 200 chars): %s
Connection failed, trying again in %d minutes
- WebSocket closed; The client-token could be invalidated, please re-login
+ User action required: Please login, the authentication token isn\'t valid anymore.
+ Connection closed, trying to establish a new one.
Received %d messages while being disconnected
Delete all
Delete logs