Keep web socket open during calling to improve message delivery.

This commit is contained in:
Cody Henthorne 2022-08-03 14:16:20 -04:00 committed by Greyson Parrelli
parent 120dda6e68
commit b002235ef7
3 changed files with 74 additions and 5 deletions

View file

@ -27,12 +27,15 @@ import org.thoughtcrime.securesms.messages.IncomingMessageProcessor.Processor;
import org.thoughtcrime.securesms.notifications.NotificationChannels; import org.thoughtcrime.securesms.notifications.NotificationChannels;
import org.thoughtcrime.securesms.push.SignalServiceNetworkAccess; import org.thoughtcrime.securesms.push.SignalServiceNetworkAccess;
import org.thoughtcrime.securesms.util.AppForegroundObserver; import org.thoughtcrime.securesms.util.AppForegroundObserver;
import org.thoughtcrime.securesms.util.Util;
import org.whispersystems.signalservice.api.SignalWebSocket; import org.whispersystems.signalservice.api.SignalWebSocket;
import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope; import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope;
import org.whispersystems.signalservice.api.websocket.WebSocketUnavailableException; import org.whispersystems.signalservice.api.websocket.WebSocketUnavailableException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@ -50,6 +53,7 @@ public class IncomingMessageObserver {
public static final int FOREGROUND_ID = 313399; public static final int FOREGROUND_ID = 313399;
private static final long REQUEST_TIMEOUT_MINUTES = 1; private static final long REQUEST_TIMEOUT_MINUTES = 1;
private static final long OLD_REQUEST_WINDOW_MS = TimeUnit.MINUTES.toMillis(5);
private static final AtomicInteger INSTANCE_COUNT = new AtomicInteger(0); private static final AtomicInteger INSTANCE_COUNT = new AtomicInteger(0);
@ -57,6 +61,7 @@ public class IncomingMessageObserver {
private final SignalServiceNetworkAccess networkAccess; private final SignalServiceNetworkAccess networkAccess;
private final List<Runnable> decryptionDrainedListeners; private final List<Runnable> decryptionDrainedListeners;
private final BroadcastReceiver connectionReceiver; private final BroadcastReceiver connectionReceiver;
private final Map<String, Long> keepAliveTokens;
private boolean appVisible; private boolean appVisible;
@ -72,6 +77,7 @@ public class IncomingMessageObserver {
this.context = context; this.context = context;
this.networkAccess = ApplicationDependencies.getSignalServiceNetworkAccess(); this.networkAccess = ApplicationDependencies.getSignalServiceNetworkAccess();
this.decryptionDrainedListeners = new CopyOnWriteArrayList<>(); this.decryptionDrainedListeners = new CopyOnWriteArrayList<>();
this.keepAliveTokens = new HashMap<>();
new MessageRetrievalThread().start(); new MessageRetrievalThread().start();
@ -155,12 +161,18 @@ public class IncomingMessageObserver {
boolean fcmEnabled = SignalStore.account().isFcmEnabled(); boolean fcmEnabled = SignalStore.account().isFcmEnabled();
boolean hasNetwork = NetworkConstraint.isMet(context); boolean hasNetwork = NetworkConstraint.isMet(context);
boolean hasProxy = SignalStore.proxy().isProxyEnabled(); boolean hasProxy = SignalStore.proxy().isProxyEnabled();
long oldRequest = System.currentTimeMillis() - OLD_REQUEST_WINDOW_MS;
Log.d(TAG, String.format("Network: %s, Foreground: %s, FCM: %s, Censored: %s, Registered: %s, Proxy: %s", boolean removedRequests = keepAliveTokens.entrySet().removeIf(e -> e.getValue() < oldRequest);
hasNetwork, appVisible, fcmEnabled, networkAccess.isCensored(), registered, hasProxy)); if (removedRequests) {
Log.d(TAG, "Removed old keep web socket open requests.");
}
Log.d(TAG, String.format("Network: %s, Foreground: %s, FCM: %s, Stay open requests: [%s], Censored: %s, Registered: %s, Proxy: %s",
hasNetwork, appVisible, fcmEnabled, Util.join(keepAliveTokens.entrySet(), ","), networkAccess.isCensored(), registered, hasProxy));
return registered && return registered &&
(appVisible || !fcmEnabled) && (appVisible || !fcmEnabled || Util.hasItems(keepAliveTokens)) &&
hasNetwork && hasNetwork &&
!networkAccess.isCensored(); !networkAccess.isCensored();
} }
@ -189,6 +201,16 @@ public class IncomingMessageObserver {
ApplicationDependencies.getSignalWebSocket().disconnect(); ApplicationDependencies.getSignalWebSocket().disconnect();
} }
public synchronized void registerKeepAliveToken(String key) {
keepAliveTokens.put(key, System.currentTimeMillis());
notifyAll();
}
public synchronized void removeKeepAliveToken(String key) {
keepAliveTokens.remove(key);
notifyAll();
}
private class MessageRetrievalThread extends Thread implements Thread.UncaughtExceptionHandler { private class MessageRetrievalThread extends Thread implements Thread.UncaughtExceptionHandler {
MessageRetrievalThread() { MessageRetrievalThread() {

View file

@ -14,10 +14,12 @@ import android.os.IBinder;
import android.telephony.PhoneStateListener; import android.telephony.PhoneStateListener;
import android.telephony.TelephonyManager; import android.telephony.TelephonyManager;
import androidx.annotation.MainThread;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat; import androidx.core.content.ContextCompat;
import org.signal.core.util.ThreadUtil;
import org.signal.core.util.logging.Log; import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.Recipient;
@ -31,6 +33,7 @@ import org.thoughtcrime.securesms.webrtc.locks.LockManager;
import java.util.Objects; import java.util.Objects;
import java.util.Set; import java.util.Set;
import java.util.concurrent.TimeUnit;
/** /**
* Provide a foreground service for {@link SignalCallManager} to leverage to run in the background when necessary. Also * Provide a foreground service for {@link SignalCallManager} to leverage to run in the background when necessary. Also
@ -38,7 +41,8 @@ import java.util.Set;
*/ */
public final class WebRtcCallService extends Service implements SignalAudioManager.EventListener { public final class WebRtcCallService extends Service implements SignalAudioManager.EventListener {
private static final String TAG = Log.tag(WebRtcCallService.class); private static final String TAG = Log.tag(WebRtcCallService.class);
private static final String WEBSOCKET_KEEP_ALIVE_TOKEN = WebRtcCallService.class.getName();
private static final String ACTION_UPDATE = "UPDATE"; private static final String ACTION_UPDATE = "UPDATE";
private static final String ACTION_STOP = "STOP"; private static final String ACTION_STOP = "STOP";
@ -52,7 +56,10 @@ public final class WebRtcCallService extends Service implements SignalAudioManag
private static final String EXTRA_ENABLED = "ENABLED"; private static final String EXTRA_ENABLED = "ENABLED";
private static final String EXTRA_AUDIO_COMMAND = "AUDIO_COMMAND"; private static final String EXTRA_AUDIO_COMMAND = "AUDIO_COMMAND";
private static final int INVALID_NOTIFICATION_ID = -1; private static final int INVALID_NOTIFICATION_ID = -1;
private static final long REQUEST_WEBSOCKET_STAY_OPEN_DELAY = TimeUnit.MINUTES.toMillis(1);
private final WebSocketKeepAliveTask webSocketKeepAliveTask = new WebSocketKeepAliveTask();
private SignalCallManager callManager; private SignalCallManager callManager;
@ -147,15 +154,20 @@ public final class WebRtcCallService extends Service implements SignalAudioManag
if (!AndroidTelecomUtil.getTelecomSupported()) { if (!AndroidTelecomUtil.getTelecomSupported()) {
TelephonyUtil.getManager(this).listen(hangUpRtcOnDeviceCallAnswered, PhoneStateListener.LISTEN_NONE); TelephonyUtil.getManager(this).listen(hangUpRtcOnDeviceCallAnswered, PhoneStateListener.LISTEN_NONE);
} }
webSocketKeepAliveTask.stop();
} }
@Override @Override
public int onStartCommand(Intent intent, int flags, int startId) { public int onStartCommand(Intent intent, int flags, int startId) {
if (intent == null || intent.getAction() == null) { if (intent == null || intent.getAction() == null) {
setCallNotification();
stop();
return START_NOT_STICKY; return START_NOT_STICKY;
} }
Log.i(TAG, "action: " + intent.getAction()); Log.i(TAG, "action: " + intent.getAction());
webSocketKeepAliveTask.start();
switch (intent.getAction()) { switch (intent.getAction()) {
case ACTION_UPDATE: case ACTION_UPDATE:
@ -296,6 +308,37 @@ public final class WebRtcCallService extends Service implements SignalAudioManag
} }
} }
/**
* Periodically request the web socket stay open if we are doing anything call related.
*/
private class WebSocketKeepAliveTask implements Runnable {
private boolean keepRunning = false;
@MainThread
public void start() {
if (!keepRunning) {
keepRunning = true;
run();
}
}
@MainThread
public void stop() {
keepRunning = false;
ThreadUtil.cancelRunnableOnMain(webSocketKeepAliveTask);
ApplicationDependencies.getIncomingMessageObserver().removeKeepAliveToken(WEBSOCKET_KEEP_ALIVE_TOKEN);
}
@MainThread
@Override
public void run() {
if (keepRunning) {
ApplicationDependencies.getIncomingMessageObserver().registerKeepAliveToken(WEBSOCKET_KEEP_ALIVE_TOKEN);
ThreadUtil.runOnMainDelayed(this, REQUEST_WEBSOCKET_STAY_OPEN_DELAY);
}
}
}
private static class NetworkReceiver extends BroadcastReceiver { private static class NetworkReceiver extends BroadcastReceiver {
@Override @Override
public void onReceive(Context context, Intent intent) { public void onReceive(Context context, Intent intent) {

View file

@ -161,6 +161,10 @@ public class Util {
return collection != null && !collection.isEmpty(); return collection != null && !collection.isEmpty();
} }
public static <K, V> boolean hasItems(@Nullable Map<K, V> map) {
return map != null && !map.isEmpty();
}
public static <K, V> V getOrDefault(@NonNull Map<K, V> map, K key, V defaultValue) { public static <K, V> V getOrDefault(@NonNull Map<K, V> map, K key, V defaultValue) {
return map.containsKey(key) ? map.get(key) : defaultValue; return map.containsKey(key) ? map.get(key) : defaultValue;
} }