mirror of
https://github.com/signalapp/Signal-Android.git
synced 2025-12-26 05:58:09 +00:00
We've been seeing a lot of socket timeouts on REST requests under certain conditions. The issue seems to be a problem with the OkHttp client. Through testing, I've seen that resetting the receiver and retrying again seems to resolve most issues.
211 lines
7.3 KiB
Java
211 lines
7.3 KiB
Java
package org.thoughtcrime.securesms.service;
|
|
|
|
import android.app.Service;
|
|
import androidx.lifecycle.DefaultLifecycleObserver;
|
|
import androidx.lifecycle.LifecycleOwner;
|
|
import androidx.lifecycle.ProcessLifecycleOwner;
|
|
import android.content.Context;
|
|
import android.content.Intent;
|
|
import android.os.IBinder;
|
|
import androidx.annotation.NonNull;
|
|
import androidx.annotation.Nullable;
|
|
import androidx.core.app.NotificationCompat;
|
|
import androidx.core.content.ContextCompat;
|
|
|
|
import org.thoughtcrime.securesms.IncomingMessageProcessor.Processor;
|
|
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
|
import org.thoughtcrime.securesms.jobmanager.ConstraintObserver;
|
|
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
|
|
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraintObserver;
|
|
import org.thoughtcrime.securesms.logging.Log;
|
|
|
|
import org.thoughtcrime.securesms.ApplicationContext;
|
|
import org.thoughtcrime.securesms.R;
|
|
import org.thoughtcrime.securesms.notifications.NotificationChannels;
|
|
import org.thoughtcrime.securesms.push.SignalServiceNetworkAccess;
|
|
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
|
import org.whispersystems.libsignal.InvalidVersionException;
|
|
import org.whispersystems.signalservice.api.SignalServiceMessagePipe;
|
|
import org.whispersystems.signalservice.api.SignalServiceMessageReceiver;
|
|
|
|
import java.util.concurrent.TimeUnit;
|
|
import java.util.concurrent.TimeoutException;
|
|
|
|
public class IncomingMessageObserver implements ConstraintObserver.Notifier {
|
|
|
|
private static final String TAG = IncomingMessageObserver.class.getSimpleName();
|
|
|
|
public static final int FOREGROUND_ID = 313399;
|
|
private static final long REQUEST_TIMEOUT_MINUTES = 1;
|
|
|
|
private static SignalServiceMessagePipe pipe = null;
|
|
private static SignalServiceMessagePipe unidentifiedPipe = null;
|
|
|
|
private final Context context;
|
|
private final NetworkConstraint networkConstraint;
|
|
private final SignalServiceNetworkAccess networkAccess;
|
|
|
|
private boolean appVisible;
|
|
|
|
|
|
public IncomingMessageObserver(@NonNull Context context) {
|
|
this.context = context;
|
|
this.networkConstraint = new NetworkConstraint.Factory(ApplicationContext.getInstance(context)).create();
|
|
this.networkAccess = ApplicationDependencies.getSignalServiceNetworkAccess();
|
|
|
|
new NetworkConstraintObserver(ApplicationContext.getInstance(context)).register(this);
|
|
new MessageRetrievalThread().start();
|
|
|
|
if (TextSecurePreferences.isFcmDisabled(context)) {
|
|
ContextCompat.startForegroundService(context, new Intent(context, ForegroundService.class));
|
|
}
|
|
|
|
ProcessLifecycleOwner.get().getLifecycle().addObserver(new DefaultLifecycleObserver() {
|
|
@Override
|
|
public void onStart(@NonNull LifecycleOwner owner) {
|
|
onAppForegrounded();
|
|
}
|
|
|
|
@Override
|
|
public void onStop(@NonNull LifecycleOwner owner) {
|
|
onAppBackgrounded();
|
|
}
|
|
});
|
|
}
|
|
|
|
@Override
|
|
public void onConstraintMet(@NonNull String reason) {
|
|
synchronized (this) {
|
|
notifyAll();
|
|
}
|
|
}
|
|
|
|
private synchronized void onAppForegrounded() {
|
|
appVisible = true;
|
|
notifyAll();
|
|
}
|
|
|
|
private synchronized void onAppBackgrounded() {
|
|
appVisible = false;
|
|
notifyAll();
|
|
}
|
|
|
|
private synchronized boolean isConnectionNecessary() {
|
|
boolean isGcmDisabled = TextSecurePreferences.isFcmDisabled(context);
|
|
|
|
Log.d(TAG, String.format("Network requirement: %s, app visible: %s, gcm disabled: %b",
|
|
networkConstraint.isMet(), appVisible, isGcmDisabled));
|
|
|
|
return TextSecurePreferences.isPushRegistered(context) &&
|
|
TextSecurePreferences.isWebsocketRegistered(context) &&
|
|
(appVisible || isGcmDisabled) &&
|
|
networkConstraint.isMet() &&
|
|
!networkAccess.isCensored(context);
|
|
}
|
|
|
|
private synchronized void waitForConnectionNecessary() {
|
|
try {
|
|
while (!isConnectionNecessary()) wait();
|
|
} catch (InterruptedException e) {
|
|
throw new AssertionError(e);
|
|
}
|
|
}
|
|
|
|
private void shutdown(SignalServiceMessagePipe pipe, SignalServiceMessagePipe unidentifiedPipe) {
|
|
try {
|
|
pipe.shutdown();
|
|
unidentifiedPipe.shutdown();
|
|
} catch (Throwable t) {
|
|
Log.w(TAG, t);
|
|
}
|
|
}
|
|
|
|
public static @Nullable SignalServiceMessagePipe getPipe() {
|
|
return pipe;
|
|
}
|
|
|
|
public static @Nullable SignalServiceMessagePipe getUnidentifiedPipe() {
|
|
return unidentifiedPipe;
|
|
}
|
|
|
|
private class MessageRetrievalThread extends Thread implements Thread.UncaughtExceptionHandler {
|
|
|
|
MessageRetrievalThread() {
|
|
super("MessageRetrievalService");
|
|
setUncaughtExceptionHandler(this);
|
|
}
|
|
|
|
@Override
|
|
public void run() {
|
|
while (true) {
|
|
Log.i(TAG, "Waiting for websocket state change....");
|
|
waitForConnectionNecessary();
|
|
|
|
Log.i(TAG, "Making websocket connection....");
|
|
SignalServiceMessageReceiver receiver = ApplicationDependencies.getSignalServiceMessageReceiver();
|
|
|
|
pipe = receiver.createMessagePipe();
|
|
unidentifiedPipe = receiver.createUnidentifiedMessagePipe();
|
|
|
|
SignalServiceMessagePipe localPipe = pipe;
|
|
SignalServiceMessagePipe unidentifiedLocalPipe = unidentifiedPipe;
|
|
|
|
try {
|
|
while (isConnectionNecessary()) {
|
|
try {
|
|
Log.i(TAG, "Reading message...");
|
|
localPipe.read(REQUEST_TIMEOUT_MINUTES, TimeUnit.MINUTES,
|
|
envelope -> {
|
|
Log.i(TAG, "Retrieved envelope! " + String.valueOf(envelope.getSource()));
|
|
try (Processor processor = ApplicationDependencies.getIncomingMessageProcessor().acquire()) {
|
|
processor.processEnvelope(envelope);
|
|
}
|
|
});
|
|
} catch (TimeoutException e) {
|
|
Log.w(TAG, "Application level read timeout...");
|
|
} catch (InvalidVersionException e) {
|
|
Log.w(TAG, e);
|
|
}
|
|
}
|
|
} catch (Throwable e) {
|
|
Log.w(TAG, e);
|
|
} finally {
|
|
Log.w(TAG, "Shutting down pipe...");
|
|
shutdown(localPipe, unidentifiedLocalPipe);
|
|
}
|
|
|
|
Log.i(TAG, "Looping...");
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void uncaughtException(Thread t, Throwable e) {
|
|
Log.w(TAG, "*** Uncaught exception!");
|
|
Log.w(TAG, e);
|
|
}
|
|
}
|
|
|
|
public static class ForegroundService extends Service {
|
|
|
|
@Override
|
|
public @Nullable IBinder onBind(Intent intent) {
|
|
return null;
|
|
}
|
|
|
|
@Override
|
|
public int onStartCommand(Intent intent, int flags, int startId) {
|
|
super.onStartCommand(intent, flags, startId);
|
|
|
|
NotificationCompat.Builder builder = new NotificationCompat.Builder(getApplicationContext(), NotificationChannels.OTHER);
|
|
builder.setContentTitle(getApplicationContext().getString(R.string.MessageRetrievalService_signal));
|
|
builder.setContentText(getApplicationContext().getString(R.string.MessageRetrievalService_background_connection_enabled));
|
|
builder.setPriority(NotificationCompat.PRIORITY_MIN);
|
|
builder.setWhen(0);
|
|
builder.setSmallIcon(R.drawable.ic_signal_background_connection);
|
|
startForeground(FOREGROUND_ID, builder.build());
|
|
|
|
return Service.START_STICKY;
|
|
}
|
|
}
|
|
}
|