mirror of
https://github.com/oxen-io/session-android.git
synced 2025-01-12 08:33:39 +00:00
Merge branch 'desktop-protocol-changes' into dev
This commit is contained in:
commit
da8bbbc9cf
@ -38,6 +38,7 @@ import org.thoughtcrime.securesms.components.TypingStatusSender;
|
|||||||
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
|
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
|
||||||
import org.thoughtcrime.securesms.crypto.MasterSecretUtil;
|
import org.thoughtcrime.securesms.crypto.MasterSecretUtil;
|
||||||
import org.thoughtcrime.securesms.crypto.ProfileKeyUtil;
|
import org.thoughtcrime.securesms.crypto.ProfileKeyUtil;
|
||||||
|
import org.thoughtcrime.securesms.crypto.storage.TextSecureSessionStore;
|
||||||
import org.thoughtcrime.securesms.database.Address;
|
import org.thoughtcrime.securesms.database.Address;
|
||||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||||
import org.thoughtcrime.securesms.dependencies.AxolotlStorageModule;
|
import org.thoughtcrime.securesms.dependencies.AxolotlStorageModule;
|
||||||
@ -91,18 +92,20 @@ import org.webrtc.PeerConnectionFactory;
|
|||||||
import org.webrtc.PeerConnectionFactory.InitializationOptions;
|
import org.webrtc.PeerConnectionFactory.InitializationOptions;
|
||||||
import org.webrtc.voiceengine.WebRtcAudioManager;
|
import org.webrtc.voiceengine.WebRtcAudioManager;
|
||||||
import org.webrtc.voiceengine.WebRtcAudioUtils;
|
import org.webrtc.voiceengine.WebRtcAudioUtils;
|
||||||
|
import org.whispersystems.libsignal.SignalProtocolAddress;
|
||||||
import org.whispersystems.libsignal.logging.SignalProtocolLoggerProvider;
|
import org.whispersystems.libsignal.logging.SignalProtocolLoggerProvider;
|
||||||
import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope;
|
import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope;
|
||||||
|
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
||||||
import org.whispersystems.signalservice.api.util.StreamDetails;
|
import org.whispersystems.signalservice.api.util.StreamDetails;
|
||||||
import org.whispersystems.signalservice.internal.push.SignalServiceProtos;
|
import org.whispersystems.signalservice.internal.push.SignalServiceProtos;
|
||||||
import org.whispersystems.signalservice.loki.api.LokiAPI;
|
import org.whispersystems.signalservice.loki.api.Poller;
|
||||||
import org.whispersystems.signalservice.loki.api.LokiPoller;
|
import org.whispersystems.signalservice.loki.api.PushNotificationAcknowledgement;
|
||||||
import org.whispersystems.signalservice.loki.api.LokiPushNotificationAcknowledgement;
|
import org.whispersystems.signalservice.loki.api.SnodeAPI;
|
||||||
import org.whispersystems.signalservice.loki.api.LokiSwarmAPI;
|
import org.whispersystems.signalservice.loki.api.SwarmAPI;
|
||||||
import org.whispersystems.signalservice.loki.api.fileserver.LokiFileServerAPI;
|
import org.whispersystems.signalservice.loki.api.fileserver.LokiFileServerAPI;
|
||||||
import org.whispersystems.signalservice.loki.api.opengroups.LokiPublicChatAPI;
|
import org.whispersystems.signalservice.loki.api.opengroups.LokiPublicChatAPI;
|
||||||
import org.whispersystems.signalservice.loki.api.p2p.LokiP2PAPI;
|
import org.whispersystems.signalservice.loki.api.shelved.p2p.LokiP2PAPI;
|
||||||
import org.whispersystems.signalservice.loki.api.p2p.LokiP2PAPIDelegate;
|
import org.whispersystems.signalservice.loki.api.shelved.p2p.LokiP2PAPIDelegate;
|
||||||
import org.whispersystems.signalservice.loki.database.LokiAPIDatabaseProtocol;
|
import org.whispersystems.signalservice.loki.database.LokiAPIDatabaseProtocol;
|
||||||
import org.whispersystems.signalservice.loki.protocol.friendrequests.FriendRequestProtocol;
|
import org.whispersystems.signalservice.loki.protocol.friendrequests.FriendRequestProtocol;
|
||||||
import org.whispersystems.signalservice.loki.protocol.mentions.MentionsManager;
|
import org.whispersystems.signalservice.loki.protocol.mentions.MentionsManager;
|
||||||
@ -151,7 +154,7 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
|
|||||||
|
|
||||||
// Loki
|
// Loki
|
||||||
public MessageNotifier messageNotifier = null;
|
public MessageNotifier messageNotifier = null;
|
||||||
public LokiPoller lokiPoller = null;
|
public Poller lokiPoller = null;
|
||||||
public LokiPublicChatManager lokiPublicChatManager = null;
|
public LokiPublicChatManager lokiPublicChatManager = null;
|
||||||
private LokiPublicChatAPI lokiPublicChatAPI = null;
|
private LokiPublicChatAPI lokiPublicChatAPI = null;
|
||||||
public Broadcaster broadcaster = null;
|
public Broadcaster broadcaster = null;
|
||||||
@ -184,8 +187,8 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
|
|||||||
String userPublicKey = TextSecurePreferences.getLocalNumber(this);
|
String userPublicKey = TextSecurePreferences.getLocalNumber(this);
|
||||||
LokiSessionResetImplementation sessionResetImpl = new LokiSessionResetImplementation(this);
|
LokiSessionResetImplementation sessionResetImpl = new LokiSessionResetImplementation(this);
|
||||||
if (userPublicKey != null) {
|
if (userPublicKey != null) {
|
||||||
LokiSwarmAPI.Companion.configureIfNeeded(apiDB);
|
SwarmAPI.Companion.configureIfNeeded(apiDB);
|
||||||
LokiAPI.Companion.configureIfNeeded(userPublicKey, apiDB, broadcaster);
|
SnodeAPI.Companion.configureIfNeeded(userPublicKey, apiDB, broadcaster);
|
||||||
FriendRequestProtocol.Companion.configureIfNeeded(apiDB, userPublicKey);
|
FriendRequestProtocol.Companion.configureIfNeeded(apiDB, userPublicKey);
|
||||||
MentionsManager.Companion.configureIfNeeded(userPublicKey, threadDB, userDB);
|
MentionsManager.Companion.configureIfNeeded(userPublicKey, threadDB, userDB);
|
||||||
SessionMetaProtocol.Companion.configureIfNeeded(apiDB, userPublicKey);
|
SessionMetaProtocol.Companion.configureIfNeeded(apiDB, userPublicKey);
|
||||||
@ -194,7 +197,7 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
|
|||||||
MultiDeviceProtocol.Companion.configureIfNeeded(apiDB);
|
MultiDeviceProtocol.Companion.configureIfNeeded(apiDB);
|
||||||
SessionManagementProtocol.Companion.configureIfNeeded(sessionResetImpl, threadDB, this);
|
SessionManagementProtocol.Companion.configureIfNeeded(sessionResetImpl, threadDB, this);
|
||||||
setUpP2PAPIIfNeeded();
|
setUpP2PAPIIfNeeded();
|
||||||
LokiPushNotificationAcknowledgement.Companion.configureIfNeeded(BuildConfig.DEBUG);
|
PushNotificationAcknowledgement.Companion.configureIfNeeded(BuildConfig.DEBUG);
|
||||||
if (setUpStorageAPIIfNeeded()) {
|
if (setUpStorageAPIIfNeeded()) {
|
||||||
if (userPublicKey != null) {
|
if (userPublicKey != null) {
|
||||||
Set<DeviceLink> deviceLinks = DatabaseFactory.getLokiAPIDatabase(this).getDeviceLinks(userPublicKey);
|
Set<DeviceLink> deviceLinks = DatabaseFactory.getLokiAPIDatabase(this).getDeviceLinks(userPublicKey);
|
||||||
@ -493,15 +496,15 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
|
|||||||
String userPublicKey = TextSecurePreferences.getLocalNumber(this);
|
String userPublicKey = TextSecurePreferences.getLocalNumber(this);
|
||||||
if (userPublicKey == null) return;
|
if (userPublicKey == null) return;
|
||||||
if (lokiPoller != null) {
|
if (lokiPoller != null) {
|
||||||
LokiAPI.shared.setUserHexEncodedPublicKey(userPublicKey);
|
SnodeAPI.shared.setUserPublicKey(userPublicKey);
|
||||||
lokiPoller.setUserHexEncodedPublicKey(userPublicKey);
|
lokiPoller.setUserPublicKey(userPublicKey);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
LokiAPIDatabase apiDB = DatabaseFactory.getLokiAPIDatabase(this);
|
LokiAPIDatabase apiDB = DatabaseFactory.getLokiAPIDatabase(this);
|
||||||
Context context = this;
|
Context context = this;
|
||||||
LokiSwarmAPI.Companion.configureIfNeeded(apiDB);
|
SwarmAPI.Companion.configureIfNeeded(apiDB);
|
||||||
LokiAPI.Companion.configureIfNeeded(userPublicKey, apiDB, broadcaster);
|
SnodeAPI.Companion.configureIfNeeded(userPublicKey, apiDB, broadcaster);
|
||||||
lokiPoller = new LokiPoller(userPublicKey, apiDB, protos -> {
|
lokiPoller = new Poller(userPublicKey, apiDB, protos -> {
|
||||||
for (SignalServiceProtos.Envelope proto : protos) {
|
for (SignalServiceProtos.Envelope proto : protos) {
|
||||||
new PushContentReceiveJob(context).processEnvelope(new SignalServiceEnvelope(proto), false);
|
new PushContentReceiveJob(context).processEnvelope(new SignalServiceEnvelope(proto), false);
|
||||||
}
|
}
|
||||||
@ -588,6 +591,18 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void sendSessionRequest(@NotNull String publicKey) {
|
public void sendSessionRequest(@NotNull String publicKey) {
|
||||||
|
// It's never necessary to establish a session with self
|
||||||
|
String userPublicKey = TextSecurePreferences.getLocalNumber(this);
|
||||||
|
if (publicKey.equals(userPublicKey)) { return; }
|
||||||
|
// Check that we don't already have a session
|
||||||
|
SignalProtocolAddress address = new SignalProtocolAddress(publicKey, SignalServiceAddress.DEFAULT_DEVICE_ID);
|
||||||
|
boolean hasSession = new TextSecureSessionStore(this).containsSession(address);
|
||||||
|
if (hasSession) { return; }
|
||||||
|
// Check that we didn't already send or process a session request
|
||||||
|
LokiAPIDatabase apiDB = DatabaseFactory.getLokiAPIDatabase(this);
|
||||||
|
boolean hasSentOrProcessedSessionRequest = (apiDB.getSessionRequestTimestamp(publicKey) != null);
|
||||||
|
if (hasSentOrProcessedSessionRequest) { return; }
|
||||||
|
// Send the session request
|
||||||
DatabaseFactory.getLokiAPIDatabase(this).setSessionRequestTimestamp(publicKey, new Date().getTime());
|
DatabaseFactory.getLokiAPIDatabase(this).setSessionRequestTimestamp(publicKey, new Date().getTime());
|
||||||
EphemeralMessage sessionRequest = EphemeralMessage.createSessionRequest(publicKey);
|
EphemeralMessage sessionRequest = EphemeralMessage.createSessionRequest(publicKey);
|
||||||
jobManager.add(new PushEphemeralMessageSendJob(sessionRequest));
|
jobManager.add(new PushEphemeralMessageSendJob(sessionRequest));
|
||||||
|
@ -44,6 +44,7 @@ import org.thoughtcrime.securesms.jobs.SmsSentJob;
|
|||||||
import org.thoughtcrime.securesms.jobs.TrimThreadJob;
|
import org.thoughtcrime.securesms.jobs.TrimThreadJob;
|
||||||
import org.thoughtcrime.securesms.jobs.TypingSendJob;
|
import org.thoughtcrime.securesms.jobs.TypingSendJob;
|
||||||
import org.thoughtcrime.securesms.jobs.UpdateApkJob;
|
import org.thoughtcrime.securesms.jobs.UpdateApkJob;
|
||||||
|
import org.thoughtcrime.securesms.loki.protocol.PushNullMessageSendJob;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@ -74,6 +75,7 @@ public class WorkManagerFactoryMappings {
|
|||||||
put(PushMediaSendJob.class.getName(), PushMediaSendJob.KEY);
|
put(PushMediaSendJob.class.getName(), PushMediaSendJob.KEY);
|
||||||
put(PushNotificationReceiveJob.class.getName(), PushNotificationReceiveJob.KEY);
|
put(PushNotificationReceiveJob.class.getName(), PushNotificationReceiveJob.KEY);
|
||||||
put(PushTextSendJob.class.getName(), PushTextSendJob.KEY);
|
put(PushTextSendJob.class.getName(), PushTextSendJob.KEY);
|
||||||
|
put(PushNullMessageSendJob.class.getName(), PushNullMessageSendJob.KEY);
|
||||||
put(RefreshAttributesJob.class.getName(), RefreshAttributesJob.KEY);
|
put(RefreshAttributesJob.class.getName(), RefreshAttributesJob.KEY);
|
||||||
put(RefreshPreKeysJob.class.getName(), RefreshPreKeysJob.KEY);
|
put(RefreshPreKeysJob.class.getName(), RefreshPreKeysJob.KEY);
|
||||||
put(RefreshUnidentifiedDeliveryAbilityJob.class.getName(), RefreshUnidentifiedDeliveryAbilityJob.KEY);
|
put(RefreshUnidentifiedDeliveryAbilityJob.class.getName(), RefreshUnidentifiedDeliveryAbilityJob.KEY);
|
||||||
|
@ -15,6 +15,7 @@ import org.thoughtcrime.securesms.jobmanager.impl.SqlCipherMigrationConstraint;
|
|||||||
import org.thoughtcrime.securesms.jobmanager.impl.SqlCipherMigrationConstraintObserver;
|
import org.thoughtcrime.securesms.jobmanager.impl.SqlCipherMigrationConstraintObserver;
|
||||||
import org.thoughtcrime.securesms.loki.protocol.MultiDeviceOpenGroupUpdateJob;
|
import org.thoughtcrime.securesms.loki.protocol.MultiDeviceOpenGroupUpdateJob;
|
||||||
import org.thoughtcrime.securesms.loki.protocol.PushEphemeralMessageSendJob;
|
import org.thoughtcrime.securesms.loki.protocol.PushEphemeralMessageSendJob;
|
||||||
|
import org.thoughtcrime.securesms.loki.protocol.PushNullMessageSendJob;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
@ -50,6 +51,7 @@ public final class JobManagerFactories {
|
|||||||
put(PushMediaSendJob.KEY, new PushMediaSendJob.Factory());
|
put(PushMediaSendJob.KEY, new PushMediaSendJob.Factory());
|
||||||
put(PushNotificationReceiveJob.KEY, new PushNotificationReceiveJob.Factory());
|
put(PushNotificationReceiveJob.KEY, new PushNotificationReceiveJob.Factory());
|
||||||
put(PushTextSendJob.KEY, new PushTextSendJob.Factory());
|
put(PushTextSendJob.KEY, new PushTextSendJob.Factory());
|
||||||
|
put(PushNullMessageSendJob.KEY, new PushNullMessageSendJob.Factory());
|
||||||
put(RefreshAttributesJob.KEY, new RefreshAttributesJob.Factory());
|
put(RefreshAttributesJob.KEY, new RefreshAttributesJob.Factory());
|
||||||
put(RefreshPreKeysJob.KEY, new RefreshPreKeysJob.Factory());
|
put(RefreshPreKeysJob.KEY, new RefreshPreKeysJob.Factory());
|
||||||
put(RefreshUnidentifiedDeliveryAbilityJob.KEY, new RefreshUnidentifiedDeliveryAbilityJob.Factory());
|
put(RefreshUnidentifiedDeliveryAbilityJob.KEY, new RefreshUnidentifiedDeliveryAbilityJob.Factory());
|
||||||
|
@ -73,6 +73,7 @@ import org.thoughtcrime.securesms.loki.protocol.FriendRequestProtocol;
|
|||||||
import org.thoughtcrime.securesms.loki.protocol.LokiSessionResetImplementation;
|
import org.thoughtcrime.securesms.loki.protocol.LokiSessionResetImplementation;
|
||||||
import org.thoughtcrime.securesms.loki.protocol.MultiDeviceProtocol;
|
import org.thoughtcrime.securesms.loki.protocol.MultiDeviceProtocol;
|
||||||
import org.thoughtcrime.securesms.loki.protocol.PushEphemeralMessageSendJob;
|
import org.thoughtcrime.securesms.loki.protocol.PushEphemeralMessageSendJob;
|
||||||
|
import org.thoughtcrime.securesms.loki.protocol.PushNullMessageSendJob;
|
||||||
import org.thoughtcrime.securesms.loki.protocol.SessionManagementProtocol;
|
import org.thoughtcrime.securesms.loki.protocol.SessionManagementProtocol;
|
||||||
import org.thoughtcrime.securesms.loki.protocol.SessionMetaProtocol;
|
import org.thoughtcrime.securesms.loki.protocol.SessionMetaProtocol;
|
||||||
import org.thoughtcrime.securesms.loki.protocol.SyncMessagesProtocol;
|
import org.thoughtcrime.securesms.loki.protocol.SyncMessagesProtocol;
|
||||||
@ -162,7 +163,6 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
|
|||||||
private MessageNotifier messageNotifier;
|
private MessageNotifier messageNotifier;
|
||||||
|
|
||||||
@Inject SignalServiceMessageSender messageSender;
|
@Inject SignalServiceMessageSender messageSender;
|
||||||
private Address author;
|
|
||||||
|
|
||||||
public PushDecryptJob(Context context) {
|
public PushDecryptJob(Context context) {
|
||||||
this(context, -1);
|
this(context, -1);
|
||||||
@ -316,51 +316,13 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
|
|||||||
} else if (isMediaMessage) {
|
} else if (isMediaMessage) {
|
||||||
handleMediaMessage(content, message, smsMessageId, Optional.absent());
|
handleMediaMessage(content, message, smsMessageId, Optional.absent());
|
||||||
|
|
||||||
// Loki - This is needed for compatibility with refactored desktop clients
|
// Loki - Handle friend request message if needed
|
||||||
if (!message.isGroupMessage()) {
|
FriendRequestProtocol.handleFriendRequestMessageIfNeeded(context, content.getSender(), content);
|
||||||
Recipient recipient = recipient(context, content.getSender());
|
|
||||||
long threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipient);
|
|
||||||
LokiThreadDatabase threadDB = DatabaseFactory.getLokiThreadDatabase(context);
|
|
||||||
LokiThreadFriendRequestStatus threadFriendRequestStatus = threadDB.getFriendRequestStatus(threadID);
|
|
||||||
if (threadFriendRequestStatus == LokiThreadFriendRequestStatus.NONE || threadFriendRequestStatus == LokiThreadFriendRequestStatus.REQUEST_EXPIRED) {
|
|
||||||
threadDB.setFriendRequestStatus(threadID, LokiThreadFriendRequestStatus.REQUEST_RECEIVED);
|
|
||||||
} else if (threadFriendRequestStatus == LokiThreadFriendRequestStatus.REQUEST_SENT) {
|
|
||||||
threadDB.setFriendRequestStatus(threadID, LokiThreadFriendRequestStatus.FRIENDS);
|
|
||||||
EphemeralMessage ephemeralMessage = EphemeralMessage.create(content.getSender());
|
|
||||||
ApplicationContext.getInstance(context).getJobManager().add(new PushEphemeralMessageSendJob(ephemeralMessage));
|
|
||||||
SyncMessagesProtocol.syncContact(context, Address.fromSerialized(content.getSender()));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Loki - Handle friend request message if needed
|
|
||||||
FriendRequestProtocol.handleFriendRequestMessageIfNeeded(context, content.getSender(), content);
|
|
||||||
}
|
|
||||||
} else if (message.getBody().isPresent()) {
|
} else if (message.getBody().isPresent()) {
|
||||||
handleTextMessage(content, message, smsMessageId, Optional.absent());
|
handleTextMessage(content, message, smsMessageId, Optional.absent());
|
||||||
|
|
||||||
// Loki - This is needed for compatibility with refactored desktop clients
|
// Loki - Handle friend request message if needed
|
||||||
if (!message.isGroupMessage()) {
|
FriendRequestProtocol.handleFriendRequestMessageIfNeeded(context, content.getSender(), content);
|
||||||
Recipient recipient = recipient(context, content.getSender());
|
|
||||||
long threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipient);
|
|
||||||
LokiThreadDatabase threadDB = DatabaseFactory.getLokiThreadDatabase(context);
|
|
||||||
LokiThreadFriendRequestStatus threadFriendRequestStatus = threadDB.getFriendRequestStatus(threadID);
|
|
||||||
if (threadFriendRequestStatus == LokiThreadFriendRequestStatus.NONE || threadFriendRequestStatus == LokiThreadFriendRequestStatus.REQUEST_EXPIRED) {
|
|
||||||
threadDB.setFriendRequestStatus(threadID, LokiThreadFriendRequestStatus.REQUEST_RECEIVED);
|
|
||||||
} else if (threadFriendRequestStatus == LokiThreadFriendRequestStatus.REQUEST_SENT) {
|
|
||||||
threadDB.setFriendRequestStatus(threadID, LokiThreadFriendRequestStatus.FRIENDS);
|
|
||||||
EphemeralMessage ephemeralMessage = EphemeralMessage.create(content.getSender());
|
|
||||||
ApplicationContext.getInstance(context).getJobManager().add(new PushEphemeralMessageSendJob(ephemeralMessage));
|
|
||||||
SyncMessagesProtocol.syncContact(context, Address.fromSerialized(content.getSender()));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Loki - Handle friend request message if needed
|
|
||||||
FriendRequestProtocol.handleFriendRequestMessageIfNeeded(context, content.getSender(), content);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Loki - This is needed for compatibility with refactored desktop clients
|
|
||||||
if (envelope.isFriendRequest()) {
|
|
||||||
EphemeralMessage ephemeralMessage = EphemeralMessage.create(content.getSender());
|
|
||||||
ApplicationContext.getInstance(context).getJobManager().add(new PushEphemeralMessageSendJob(ephemeralMessage));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (message.getGroupInfo().isPresent() && groupDatabase.isUnknownGroup(GroupUtil.getEncodedId(message.getGroupInfo().get()))) {
|
if (message.getGroupInfo().isPresent() && groupDatabase.isUnknownGroup(GroupUtil.getEncodedId(message.getGroupInfo().get()))) {
|
||||||
@ -406,7 +368,27 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
|
|||||||
} else if (content.getTypingMessage().isPresent()) {
|
} else if (content.getTypingMessage().isPresent()) {
|
||||||
handleTypingMessage(content, content.getTypingMessage().get());
|
handleTypingMessage(content, content.getTypingMessage().get());
|
||||||
} else {
|
} else {
|
||||||
Log.w(TAG, "Got unrecognized message...");
|
|
||||||
|
// Loki - This is needed for compatibility with refactored desktop clients
|
||||||
|
// ========
|
||||||
|
if (content.isFriendRequest()) {
|
||||||
|
ApplicationContext.getInstance(context).getJobManager().add(new PushNullMessageSendJob(content.getSender()));
|
||||||
|
} else {
|
||||||
|
Log.w(TAG, "Got unrecognized message...");
|
||||||
|
}
|
||||||
|
Recipient recipient = recipient(context, content.getSender());
|
||||||
|
long threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipient);
|
||||||
|
LokiThreadDatabase threadDB = DatabaseFactory.getLokiThreadDatabase(context);
|
||||||
|
LokiThreadFriendRequestStatus threadFriendRequestStatus = threadDB.getFriendRequestStatus(threadID);
|
||||||
|
if (threadFriendRequestStatus == LokiThreadFriendRequestStatus.NONE || threadFriendRequestStatus == LokiThreadFriendRequestStatus.REQUEST_EXPIRED) {
|
||||||
|
threadDB.setFriendRequestStatus(threadID, LokiThreadFriendRequestStatus.REQUEST_RECEIVED);
|
||||||
|
} else if (threadFriendRequestStatus == LokiThreadFriendRequestStatus.REQUEST_SENT) {
|
||||||
|
threadDB.setFriendRequestStatus(threadID, LokiThreadFriendRequestStatus.FRIENDS);
|
||||||
|
EphemeralMessage ephemeralMessage = EphemeralMessage.create(content.getSender());
|
||||||
|
ApplicationContext.getInstance(context).getJobManager().add(new PushEphemeralMessageSendJob(ephemeralMessage));
|
||||||
|
SyncMessagesProtocol.syncContact(context, Address.fromSerialized(content.getSender()));
|
||||||
|
}
|
||||||
|
// ========
|
||||||
}
|
}
|
||||||
|
|
||||||
resetRecipientToPush(Recipient.from(context, Address.fromSerialized(content.getSender()), false));
|
resetRecipientToPush(Recipient.from(context, Address.fromSerialized(content.getSender()), false));
|
||||||
|
@ -44,7 +44,7 @@ import org.whispersystems.signalservice.api.messages.multidevice.SignalServiceSy
|
|||||||
import org.whispersystems.signalservice.api.messages.shared.SharedContact;
|
import org.whispersystems.signalservice.api.messages.shared.SharedContact;
|
||||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
||||||
import org.whispersystems.signalservice.api.push.exceptions.UnregisteredUserException;
|
import org.whispersystems.signalservice.api.push.exceptions.UnregisteredUserException;
|
||||||
import org.whispersystems.signalservice.loki.api.LokiAPI;
|
import org.whispersystems.signalservice.loki.api.SnodeAPI;
|
||||||
import org.whispersystems.signalservice.loki.protocol.meta.SessionMetaProtocol;
|
import org.whispersystems.signalservice.loki.protocol.meta.SessionMetaProtocol;
|
||||||
|
|
||||||
import java.io.FileNotFoundException;
|
import java.io.FileNotFoundException;
|
||||||
@ -231,7 +231,7 @@ public class PushMediaSendJob extends PushSendJob implements InjectableType {
|
|||||||
database.addMismatchedIdentity(messageId, Address.fromSerialized(uie.getE164Number()), uie.getIdentityKey());
|
database.addMismatchedIdentity(messageId, Address.fromSerialized(uie.getE164Number()), uie.getIdentityKey());
|
||||||
database.markAsSentFailed(messageId);
|
database.markAsSentFailed(messageId);
|
||||||
}
|
}
|
||||||
} catch (LokiAPI.Error e) {
|
} catch (SnodeAPI.Error e) {
|
||||||
Log.d("Loki", "Couldn't send message due to error: " + e.getDescription());
|
Log.d("Loki", "Couldn't send message due to error: " + e.getDescription());
|
||||||
if (messageId >= 0) {
|
if (messageId >= 0) {
|
||||||
LokiMessageDatabase lokiMessageDatabase = DatabaseFactory.getLokiMessageDatabase(context);
|
LokiMessageDatabase lokiMessageDatabase = DatabaseFactory.getLokiMessageDatabase(context);
|
||||||
@ -257,7 +257,7 @@ public class PushMediaSendJob extends PushSendJob implements InjectableType {
|
|||||||
|
|
||||||
private boolean deliver(OutgoingMediaMessage message)
|
private boolean deliver(OutgoingMediaMessage message)
|
||||||
throws RetryLaterException, InsecureFallbackApprovalException, UntrustedIdentityException,
|
throws RetryLaterException, InsecureFallbackApprovalException, UntrustedIdentityException,
|
||||||
UndeliverableMessageException, LokiAPI.Error
|
UndeliverableMessageException, SnodeAPI.Error
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
Recipient recipient = Recipient.from(context, destination, false);
|
Recipient recipient = Recipient.from(context, destination, false);
|
||||||
|
@ -32,7 +32,7 @@ import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage;
|
|||||||
import org.whispersystems.signalservice.api.messages.multidevice.SignalServiceSyncMessage;
|
import org.whispersystems.signalservice.api.messages.multidevice.SignalServiceSyncMessage;
|
||||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
||||||
import org.whispersystems.signalservice.api.push.exceptions.UnregisteredUserException;
|
import org.whispersystems.signalservice.api.push.exceptions.UnregisteredUserException;
|
||||||
import org.whispersystems.signalservice.loki.api.LokiAPI;
|
import org.whispersystems.signalservice.loki.api.SnodeAPI;
|
||||||
import org.whispersystems.signalservice.loki.protocol.meta.SessionMetaProtocol;
|
import org.whispersystems.signalservice.loki.protocol.meta.SessionMetaProtocol;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@ -173,7 +173,7 @@ public class PushTextSendJob extends PushSendJob implements InjectableType {
|
|||||||
database.markAsSentFailed(record.getId());
|
database.markAsSentFailed(record.getId());
|
||||||
database.markAsPush(record.getId());
|
database.markAsPush(record.getId());
|
||||||
}
|
}
|
||||||
} catch (LokiAPI.Error e) {
|
} catch (SnodeAPI.Error e) {
|
||||||
Log.d("Loki", "Couldn't send message due to error: " + e.getDescription());
|
Log.d("Loki", "Couldn't send message due to error: " + e.getDescription());
|
||||||
if (messageId >= 0) {
|
if (messageId >= 0) {
|
||||||
LokiMessageDatabase lokiMessageDatabase = DatabaseFactory.getLokiMessageDatabase(context);
|
LokiMessageDatabase lokiMessageDatabase = DatabaseFactory.getLokiMessageDatabase(context);
|
||||||
@ -204,7 +204,7 @@ public class PushTextSendJob extends PushSendJob implements InjectableType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private boolean deliver(SmsMessageRecord message)
|
private boolean deliver(SmsMessageRecord message)
|
||||||
throws UntrustedIdentityException, InsecureFallbackApprovalException, RetryLaterException, LokiAPI.Error
|
throws UntrustedIdentityException, InsecureFallbackApprovalException, RetryLaterException, SnodeAPI.Error
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
Recipient recipient = Recipient.from(context, destination, false);
|
Recipient recipient = Recipient.from(context, destination, false);
|
||||||
|
@ -9,7 +9,7 @@ import org.thoughtcrime.securesms.jobs.PushContentReceiveJob
|
|||||||
import org.thoughtcrime.securesms.service.PersistentAlarmManagerListener
|
import org.thoughtcrime.securesms.service.PersistentAlarmManagerListener
|
||||||
import org.thoughtcrime.securesms.util.TextSecurePreferences
|
import org.thoughtcrime.securesms.util.TextSecurePreferences
|
||||||
import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope
|
import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope
|
||||||
import org.whispersystems.signalservice.loki.api.LokiAPI
|
import org.whispersystems.signalservice.loki.api.SnodeAPI
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
class BackgroundPollWorker : PersistentAlarmManagerListener() {
|
class BackgroundPollWorker : PersistentAlarmManagerListener() {
|
||||||
@ -35,8 +35,8 @@ class BackgroundPollWorker : PersistentAlarmManagerListener() {
|
|||||||
try {
|
try {
|
||||||
val applicationContext = context.applicationContext as ApplicationContext
|
val applicationContext = context.applicationContext as ApplicationContext
|
||||||
val broadcaster = applicationContext.broadcaster
|
val broadcaster = applicationContext.broadcaster
|
||||||
LokiAPI.configureIfNeeded(userPublicKey, lokiAPIDatabase, broadcaster)
|
SnodeAPI.configureIfNeeded(userPublicKey, lokiAPIDatabase, broadcaster)
|
||||||
LokiAPI.shared.getMessages().map { messages ->
|
SnodeAPI.shared.getMessages().map { messages ->
|
||||||
messages.forEach {
|
messages.forEach {
|
||||||
PushContentReceiveJob(context).processEnvelope(SignalServiceEnvelope(it), false)
|
PushContentReceiveJob(context).processEnvelope(SignalServiceEnvelope(it), false)
|
||||||
}
|
}
|
||||||
|
@ -166,7 +166,7 @@ class LokiPublicChatPoller(private val context: Context, private val group: Loki
|
|||||||
}
|
}
|
||||||
val senderHexEncodedPublicKey = masterHexEncodedPublicKey ?: message.hexEncodedPublicKey
|
val senderHexEncodedPublicKey = masterHexEncodedPublicKey ?: message.hexEncodedPublicKey
|
||||||
val serviceDataMessage = getDataMessage(message)
|
val serviceDataMessage = getDataMessage(message)
|
||||||
val serviceContent = SignalServiceContent(serviceDataMessage, senderHexEncodedPublicKey, SignalServiceAddress.DEFAULT_DEVICE_ID, message.timestamp, false, false, false, false, false)
|
val serviceContent = SignalServiceContent(serviceDataMessage, senderHexEncodedPublicKey, SignalServiceAddress.DEFAULT_DEVICE_ID, message.timestamp, false, false, false, false)
|
||||||
if (serviceDataMessage.quote.isPresent || (serviceDataMessage.attachments.isPresent && serviceDataMessage.attachments.get().size > 0) || serviceDataMessage.previews.isPresent) {
|
if (serviceDataMessage.quote.isPresent || (serviceDataMessage.attachments.isPresent && serviceDataMessage.attachments.get().size > 0) || serviceDataMessage.previews.isPresent) {
|
||||||
PushDecryptJob(context).handleMediaMessage(serviceContent, serviceDataMessage, Optional.absent(), Optional.of(message.serverID))
|
PushDecryptJob(context).handleMediaMessage(serviceContent, serviceDataMessage, Optional.absent(), Optional.of(message.serverID))
|
||||||
} else {
|
} else {
|
||||||
|
@ -5,14 +5,14 @@ import okhttp3.*
|
|||||||
import org.thoughtcrime.securesms.util.TextSecurePreferences
|
import org.thoughtcrime.securesms.util.TextSecurePreferences
|
||||||
import org.whispersystems.libsignal.logging.Log
|
import org.whispersystems.libsignal.logging.Log
|
||||||
import org.whispersystems.signalservice.internal.util.JsonUtil
|
import org.whispersystems.signalservice.internal.util.JsonUtil
|
||||||
import org.whispersystems.signalservice.loki.api.LokiPushNotificationAcknowledgement
|
import org.whispersystems.signalservice.loki.api.PushNotificationAcknowledgement
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
|
|
||||||
object LokiPushNotificationManager {
|
object LokiPushNotificationManager {
|
||||||
private val connection = OkHttpClient()
|
private val connection = OkHttpClient()
|
||||||
|
|
||||||
private val server by lazy {
|
private val server by lazy {
|
||||||
LokiPushNotificationAcknowledgement.shared.server
|
PushNotificationAcknowledgement.shared.server
|
||||||
}
|
}
|
||||||
|
|
||||||
private const val tokenExpirationInterval = 12 * 60 * 60 * 1000
|
private const val tokenExpirationInterval = 12 * 60 * 60 * 1000
|
||||||
|
@ -7,7 +7,7 @@ import org.thoughtcrime.securesms.util.TextSecurePreferences
|
|||||||
import org.whispersystems.libsignal.logging.Log
|
import org.whispersystems.libsignal.logging.Log
|
||||||
import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope
|
import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope
|
||||||
import org.whispersystems.signalservice.internal.util.Base64
|
import org.whispersystems.signalservice.internal.util.Base64
|
||||||
import org.whispersystems.signalservice.loki.api.LokiMessageWrapper
|
import org.whispersystems.signalservice.loki.api.MessageWrapper
|
||||||
|
|
||||||
class PushNotificationService : FirebaseMessagingService() {
|
class PushNotificationService : FirebaseMessagingService() {
|
||||||
|
|
||||||
@ -23,7 +23,7 @@ class PushNotificationService : FirebaseMessagingService() {
|
|||||||
val data = base64EncodedData?.let { Base64.decode(it) }
|
val data = base64EncodedData?.let { Base64.decode(it) }
|
||||||
if (data != null) {
|
if (data != null) {
|
||||||
try {
|
try {
|
||||||
val envelope = LokiMessageWrapper.unwrap(data)
|
val envelope = MessageWrapper.unwrap(data)
|
||||||
PushContentReceiveJob(this).processEnvelope(SignalServiceEnvelope(envelope), true)
|
PushContentReceiveJob(this).processEnvelope(SignalServiceEnvelope(envelope), true)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.d("Loki", "Failed to unwrap data for message.")
|
Log.d("Loki", "Failed to unwrap data for message.")
|
||||||
|
@ -8,7 +8,7 @@ import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper
|
|||||||
import org.thoughtcrime.securesms.loki.utilities.*
|
import org.thoughtcrime.securesms.loki.utilities.*
|
||||||
import org.thoughtcrime.securesms.util.Base64
|
import org.thoughtcrime.securesms.util.Base64
|
||||||
import org.thoughtcrime.securesms.util.TextSecurePreferences
|
import org.thoughtcrime.securesms.util.TextSecurePreferences
|
||||||
import org.whispersystems.signalservice.loki.api.LokiAPITarget
|
import org.whispersystems.signalservice.loki.api.Snode
|
||||||
import org.whispersystems.signalservice.loki.database.LokiAPIDatabaseProtocol
|
import org.whispersystems.signalservice.loki.database.LokiAPIDatabaseProtocol
|
||||||
import org.whispersystems.signalservice.loki.protocol.multidevice.DeviceLink
|
import org.whispersystems.signalservice.loki.protocol.multidevice.DeviceLink
|
||||||
|
|
||||||
@ -77,7 +77,7 @@ class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(
|
|||||||
@JvmStatic val createSessionRequestTimestampCacheCommand = "CREATE TABLE $sessionRequestTimestampCache ($publicKey STRING PRIMARY KEY, $timestamp INTEGER DEFAULT 0);"
|
@JvmStatic val createSessionRequestTimestampCacheCommand = "CREATE TABLE $sessionRequestTimestampCache ($publicKey STRING PRIMARY KEY, $timestamp INTEGER DEFAULT 0);"
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getSnodePool(): Set<LokiAPITarget> {
|
override fun getSnodePool(): Set<Snode> {
|
||||||
val database = databaseHelper.readableDatabase
|
val database = databaseHelper.readableDatabase
|
||||||
return database.get(snodePoolCache, "${Companion.dummyKey} = ?", wrap("dummy_key")) { cursor ->
|
return database.get(snodePoolCache, "${Companion.dummyKey} = ?", wrap("dummy_key")) { cursor ->
|
||||||
val snodePoolAsString = cursor.getString(cursor.getColumnIndexOrThrow(snodePool))
|
val snodePoolAsString = cursor.getString(cursor.getColumnIndexOrThrow(snodePool))
|
||||||
@ -87,12 +87,12 @@ class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(
|
|||||||
val port = components.getOrNull(1)?.toIntOrNull() ?: return@mapNotNull null
|
val port = components.getOrNull(1)?.toIntOrNull() ?: return@mapNotNull null
|
||||||
val ed25519Key = components.getOrNull(2) ?: return@mapNotNull null
|
val ed25519Key = components.getOrNull(2) ?: return@mapNotNull null
|
||||||
val x25519Key = components.getOrNull(3) ?: return@mapNotNull null
|
val x25519Key = components.getOrNull(3) ?: return@mapNotNull null
|
||||||
LokiAPITarget(address, port, LokiAPITarget.KeySet(ed25519Key, x25519Key))
|
Snode(address, port, Snode.KeySet(ed25519Key, x25519Key))
|
||||||
}
|
}
|
||||||
}?.toSet() ?: setOf()
|
}?.toSet() ?: setOf()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun setSnodePool(newValue: Set<LokiAPITarget>) {
|
override fun setSnodePool(newValue: Set<Snode>) {
|
||||||
val database = databaseHelper.writableDatabase
|
val database = databaseHelper.writableDatabase
|
||||||
val snodePoolAsString = newValue.joinToString(", ") { snode ->
|
val snodePoolAsString = newValue.joinToString(", ") { snode ->
|
||||||
var string = "${snode.address}-${snode.port}"
|
var string = "${snode.address}-${snode.port}"
|
||||||
@ -106,9 +106,9 @@ class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(
|
|||||||
database.insertOrUpdate(snodePoolCache, row, "${Companion.dummyKey} = ?", wrap("dummy_key"))
|
database.insertOrUpdate(snodePoolCache, row, "${Companion.dummyKey} = ?", wrap("dummy_key"))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getOnionRequestPaths(): List<List<LokiAPITarget>> {
|
override fun getOnionRequestPaths(): List<List<Snode>> {
|
||||||
val database = databaseHelper.readableDatabase
|
val database = databaseHelper.readableDatabase
|
||||||
fun get(indexPath: String): LokiAPITarget? {
|
fun get(indexPath: String): Snode? {
|
||||||
return database.get(onionRequestPathCache, "${Companion.indexPath} = ?", wrap(indexPath)) { cursor ->
|
return database.get(onionRequestPathCache, "${Companion.indexPath} = ?", wrap(indexPath)) { cursor ->
|
||||||
val snodeAsString = cursor.getString(cursor.getColumnIndexOrThrow(snode))
|
val snodeAsString = cursor.getString(cursor.getColumnIndexOrThrow(snode))
|
||||||
val components = snodeAsString.split("-")
|
val components = snodeAsString.split("-")
|
||||||
@ -117,7 +117,7 @@ class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(
|
|||||||
val ed25519Key = components.getOrNull(2)
|
val ed25519Key = components.getOrNull(2)
|
||||||
val x25519Key = components.getOrNull(3)
|
val x25519Key = components.getOrNull(3)
|
||||||
if (port != null && ed25519Key != null && x25519Key != null) {
|
if (port != null && ed25519Key != null && x25519Key != null) {
|
||||||
LokiAPITarget(address, port, LokiAPITarget.KeySet(ed25519Key, x25519Key))
|
Snode(address, port, Snode.KeySet(ed25519Key, x25519Key))
|
||||||
} else {
|
} else {
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
@ -139,7 +139,7 @@ class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(
|
|||||||
delete("1-1"); delete("1-2")
|
delete("1-1"); delete("1-2")
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun setOnionRequestPaths(newValue: List<List<LokiAPITarget>>) {
|
override fun setOnionRequestPaths(newValue: List<List<Snode>>) {
|
||||||
// FIXME: This is a bit of a dirty approach that assumes 2 paths of length 3 each. We should do better than this.
|
// FIXME: This is a bit of a dirty approach that assumes 2 paths of length 3 each. We should do better than this.
|
||||||
if (newValue.count() != 2) { return }
|
if (newValue.count() != 2) { return }
|
||||||
val path0 = newValue[0]
|
val path0 = newValue[0]
|
||||||
@ -147,7 +147,7 @@ class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(
|
|||||||
if (path0.count() != 3 || path1.count() != 3) { return }
|
if (path0.count() != 3 || path1.count() != 3) { return }
|
||||||
Log.d("Loki", "Persisting onion request paths to database.")
|
Log.d("Loki", "Persisting onion request paths to database.")
|
||||||
val database = databaseHelper.writableDatabase
|
val database = databaseHelper.writableDatabase
|
||||||
fun set(indexPath: String ,snode: LokiAPITarget) {
|
fun set(indexPath: String ,snode: Snode) {
|
||||||
var snodeAsString = "${snode.address}-${snode.port}"
|
var snodeAsString = "${snode.address}-${snode.port}"
|
||||||
val keySet = snode.publicKeySet
|
val keySet = snode.publicKeySet
|
||||||
if (keySet != null) {
|
if (keySet != null) {
|
||||||
@ -161,7 +161,7 @@ class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(
|
|||||||
set("1-1", path1[1]); set("1-2", path1[2])
|
set("1-1", path1[1]); set("1-2", path1[2])
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getSwarm(hexEncodedPublicKey: String): Set<LokiAPITarget>? {
|
override fun getSwarm(hexEncodedPublicKey: String): Set<Snode>? {
|
||||||
val database = databaseHelper.readableDatabase
|
val database = databaseHelper.readableDatabase
|
||||||
return database.get(swarmCache, "${Companion.hexEncodedPublicKey} = ?", wrap(hexEncodedPublicKey)) { cursor ->
|
return database.get(swarmCache, "${Companion.hexEncodedPublicKey} = ?", wrap(hexEncodedPublicKey)) { cursor ->
|
||||||
val swarmAsString = cursor.getString(cursor.getColumnIndexOrThrow(swarm))
|
val swarmAsString = cursor.getString(cursor.getColumnIndexOrThrow(swarm))
|
||||||
@ -171,12 +171,12 @@ class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(
|
|||||||
val port = components.getOrNull(1)?.toIntOrNull() ?: return@mapNotNull null
|
val port = components.getOrNull(1)?.toIntOrNull() ?: return@mapNotNull null
|
||||||
val ed25519Key = components.getOrNull(2) ?: return@mapNotNull null
|
val ed25519Key = components.getOrNull(2) ?: return@mapNotNull null
|
||||||
val x25519Key = components.getOrNull(3) ?: return@mapNotNull null
|
val x25519Key = components.getOrNull(3) ?: return@mapNotNull null
|
||||||
LokiAPITarget(address, port, LokiAPITarget.KeySet(ed25519Key, x25519Key))
|
Snode(address, port, Snode.KeySet(ed25519Key, x25519Key))
|
||||||
}
|
}
|
||||||
}?.toSet()
|
}?.toSet()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun setSwarm(hexEncodedPublicKey: String, newValue: Set<LokiAPITarget>) {
|
override fun setSwarm(hexEncodedPublicKey: String, newValue: Set<Snode>) {
|
||||||
val database = databaseHelper.writableDatabase
|
val database = databaseHelper.writableDatabase
|
||||||
val swarmAsString = newValue.joinToString(", ") { target ->
|
val swarmAsString = newValue.joinToString(", ") { target ->
|
||||||
var string = "${target.address}-${target.port}"
|
var string = "${target.address}-${target.port}"
|
||||||
@ -190,14 +190,14 @@ class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(
|
|||||||
database.insertOrUpdate(swarmCache, row, "${Companion.hexEncodedPublicKey} = ?", wrap(hexEncodedPublicKey))
|
database.insertOrUpdate(swarmCache, row, "${Companion.hexEncodedPublicKey} = ?", wrap(hexEncodedPublicKey))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getLastMessageHashValue(target: LokiAPITarget): String? {
|
override fun getLastMessageHashValue(target: Snode): String? {
|
||||||
val database = databaseHelper.readableDatabase
|
val database = databaseHelper.readableDatabase
|
||||||
return database.get(lastMessageHashValueCache, "${Companion.target} = ?", wrap(target.address)) { cursor ->
|
return database.get(lastMessageHashValueCache, "${Companion.target} = ?", wrap(target.address)) { cursor ->
|
||||||
cursor.getString(cursor.getColumnIndexOrThrow(lastMessageHashValue))
|
cursor.getString(cursor.getColumnIndexOrThrow(lastMessageHashValue))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun setLastMessageHashValue(target: LokiAPITarget, newValue: String) {
|
override fun setLastMessageHashValue(target: Snode, newValue: String) {
|
||||||
val database = databaseHelper.writableDatabase
|
val database = databaseHelper.writableDatabase
|
||||||
val row = wrap(mapOf(Companion.target to target.address, lastMessageHashValue to newValue))
|
val row = wrap(mapOf(Companion.target to target.address, lastMessageHashValue to newValue))
|
||||||
database.insertOrUpdate(lastMessageHashValueCache, row, "${Companion.target} = ?", wrap(target.address))
|
database.insertOrUpdate(lastMessageHashValueCache, row, "${Companion.target} = ?", wrap(target.address))
|
||||||
|
@ -21,7 +21,7 @@ import org.thoughtcrime.securesms.loki.utilities.QRCodeUtilities
|
|||||||
import org.thoughtcrime.securesms.loki.utilities.toPx
|
import org.thoughtcrime.securesms.loki.utilities.toPx
|
||||||
import org.thoughtcrime.securesms.util.TextSecurePreferences
|
import org.thoughtcrime.securesms.util.TextSecurePreferences
|
||||||
import org.thoughtcrime.securesms.util.Util
|
import org.thoughtcrime.securesms.util.Util
|
||||||
import org.whispersystems.signalservice.loki.api.LokiAPI
|
import org.whispersystems.signalservice.loki.api.SnodeAPI
|
||||||
import org.whispersystems.signalservice.loki.api.fileserver.LokiFileServerAPI
|
import org.whispersystems.signalservice.loki.api.fileserver.LokiFileServerAPI
|
||||||
import org.whispersystems.signalservice.loki.crypto.MnemonicCodec
|
import org.whispersystems.signalservice.loki.crypto.MnemonicCodec
|
||||||
import org.whispersystems.signalservice.loki.protocol.multidevice.DeviceLink
|
import org.whispersystems.signalservice.loki.protocol.multidevice.DeviceLink
|
||||||
@ -84,7 +84,7 @@ class LinkDeviceMasterModeDialog : DialogFragment(), DeviceLinkingSessionListene
|
|||||||
contentView.cancelButton.visibility = View.GONE
|
contentView.cancelButton.visibility = View.GONE
|
||||||
contentView.authorizeButton.visibility = View.GONE
|
contentView.authorizeButton.visibility = View.GONE
|
||||||
}
|
}
|
||||||
LokiFileServerAPI.shared.addDeviceLink(deviceLink).bind(LokiAPI.sharedContext) {
|
LokiFileServerAPI.shared.addDeviceLink(deviceLink).bind(SnodeAPI.sharedContext) {
|
||||||
MultiDeviceProtocol.signAndSendDeviceLinkMessage(context!!, deviceLink)
|
MultiDeviceProtocol.signAndSendDeviceLinkMessage(context!!, deviceLink)
|
||||||
}.success {
|
}.success {
|
||||||
TextSecurePreferences.setMultiDevice(context!!, true)
|
TextSecurePreferences.setMultiDevice(context!!, true)
|
||||||
|
@ -17,7 +17,7 @@ import org.whispersystems.libsignal.SignalProtocolAddress
|
|||||||
import org.whispersystems.signalservice.api.messages.SignalServiceContent
|
import org.whispersystems.signalservice.api.messages.SignalServiceContent
|
||||||
import org.whispersystems.signalservice.api.messages.SignalServiceGroup
|
import org.whispersystems.signalservice.api.messages.SignalServiceGroup
|
||||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress
|
import org.whispersystems.signalservice.api.push.SignalServiceAddress
|
||||||
import org.whispersystems.signalservice.loki.api.LokiAPI
|
import org.whispersystems.signalservice.loki.api.SnodeAPI
|
||||||
import org.whispersystems.signalservice.loki.api.fileserver.LokiFileServerAPI
|
import org.whispersystems.signalservice.loki.api.fileserver.LokiFileServerAPI
|
||||||
import org.whispersystems.signalservice.loki.protocol.multidevice.MultiDeviceProtocol
|
import org.whispersystems.signalservice.loki.protocol.multidevice.MultiDeviceProtocol
|
||||||
import java.util.*
|
import java.util.*
|
||||||
@ -106,13 +106,7 @@ object ClosedGroupsProtocol {
|
|||||||
allDevices.remove(userPublicKey)
|
allDevices.remove(userPublicKey)
|
||||||
}
|
}
|
||||||
for (device in allDevices) {
|
for (device in allDevices) {
|
||||||
val deviceAsAddress = SignalProtocolAddress(device, SignalServiceAddress.DEFAULT_DEVICE_ID)
|
ApplicationContext.getInstance(context).sendSessionRequest(device)
|
||||||
val hasSession = TextSecureSessionStore(context).containsSession(deviceAsAddress)
|
|
||||||
if (hasSession) { continue }
|
|
||||||
if (DatabaseFactory.getLokiAPIDatabase(context).getSessionRequestTimestamp(device) != null) { return }
|
|
||||||
DatabaseFactory.getLokiAPIDatabase(context).setSessionRequestTimestamp(device, Date().time)
|
|
||||||
val sessionRequest = EphemeralMessage.createSessionRequest(device)
|
|
||||||
ApplicationContext.getInstance(context).jobManager.add(PushEphemeralMessageSendJob(sessionRequest))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -0,0 +1,84 @@
|
|||||||
|
package org.thoughtcrime.securesms.loki.protocol
|
||||||
|
|
||||||
|
import com.google.protobuf.ByteString
|
||||||
|
import org.thoughtcrime.securesms.ApplicationContext
|
||||||
|
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil
|
||||||
|
import org.thoughtcrime.securesms.database.Address
|
||||||
|
import org.thoughtcrime.securesms.jobmanager.Data
|
||||||
|
import org.thoughtcrime.securesms.jobmanager.Job
|
||||||
|
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint
|
||||||
|
import org.thoughtcrime.securesms.jobs.BaseJob
|
||||||
|
import org.thoughtcrime.securesms.logging.Log
|
||||||
|
import org.thoughtcrime.securesms.recipients.Recipient
|
||||||
|
import org.whispersystems.signalservice.api.push.SignalServiceAddress
|
||||||
|
import org.whispersystems.signalservice.internal.push.SignalServiceProtos
|
||||||
|
import org.whispersystems.signalservice.loki.protocol.meta.TTLUtilities
|
||||||
|
import java.io.IOException
|
||||||
|
import java.security.SecureRandom
|
||||||
|
import java.util.*
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
|
class PushNullMessageSendJob private constructor(parameters: Parameters, private val publicKey: String) : BaseJob(parameters) {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val KEY = "PushNullMessageSendJob"
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(publicKey: String) : this(Parameters.Builder()
|
||||||
|
.addConstraint(NetworkConstraint.KEY)
|
||||||
|
.setQueue(KEY)
|
||||||
|
.setLifespan(TimeUnit.DAYS.toMillis(1))
|
||||||
|
.setMaxAttempts(1)
|
||||||
|
.build(),
|
||||||
|
publicKey)
|
||||||
|
|
||||||
|
override fun serialize(): Data {
|
||||||
|
return Data.Builder().putString("publicKey", publicKey).build()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getFactoryKey(): String { return KEY }
|
||||||
|
|
||||||
|
public override fun onRun() {
|
||||||
|
val contentMessage = SignalServiceProtos.Content.newBuilder()
|
||||||
|
val nullMessage = SignalServiceProtos.NullMessage.newBuilder()
|
||||||
|
val sr = SecureRandom()
|
||||||
|
val paddingSize = sr.nextInt(512)
|
||||||
|
val padding = ByteArray(paddingSize)
|
||||||
|
sr.nextBytes(padding)
|
||||||
|
nullMessage.padding = ByteString.copyFrom(padding)
|
||||||
|
contentMessage.nullMessage = nullMessage.build()
|
||||||
|
val serializedContentMessage = contentMessage.build().toByteArray()
|
||||||
|
val messageSender = ApplicationContext.getInstance(context).communicationModule.provideSignalMessageSender()
|
||||||
|
val address = SignalServiceAddress(publicKey)
|
||||||
|
val recipient = Recipient.from(context, Address.fromSerialized(publicKey), false)
|
||||||
|
val udAccess = UnidentifiedAccessUtil.getAccessFor(context, recipient)
|
||||||
|
val ttl = TTLUtilities.getTTL(TTLUtilities.MessageType.Ephemeral)
|
||||||
|
try {
|
||||||
|
messageSender.sendMessage(0, address, udAccess.get().targetUnidentifiedAccess,
|
||||||
|
Date().time, serializedContentMessage, false, ttl, false,
|
||||||
|
false, false, false, false)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.d("Loki", "Failed to send null message to: $publicKey due to error: $e.")
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override fun onShouldRetry(e: Exception): Boolean {
|
||||||
|
// Disable since we have our own retrying
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCanceled() { }
|
||||||
|
|
||||||
|
class Factory : Job.Factory<PushNullMessageSendJob> {
|
||||||
|
|
||||||
|
override fun create(parameters: Parameters, data: Data): PushNullMessageSendJob {
|
||||||
|
try {
|
||||||
|
val publicKey = data.getString("publicKey")
|
||||||
|
return PushNullMessageSendJob(parameters, publicKey)
|
||||||
|
} catch (e: IOException) {
|
||||||
|
throw AssertionError(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -16,7 +16,7 @@ import org.thoughtcrime.securesms.util.TextSecurePreferences
|
|||||||
import org.whispersystems.libsignal.loki.LokiSessionResetStatus
|
import org.whispersystems.libsignal.loki.LokiSessionResetStatus
|
||||||
import org.whispersystems.signalservice.api.messages.SignalServiceContent
|
import org.whispersystems.signalservice.api.messages.SignalServiceContent
|
||||||
import org.whispersystems.signalservice.loki.protocol.multidevice.MultiDeviceProtocol
|
import org.whispersystems.signalservice.loki.protocol.multidevice.MultiDeviceProtocol
|
||||||
import org.whispersystems.signalservice.loki.protocol.todo.LokiThreadFriendRequestStatus
|
import java.util.*
|
||||||
|
|
||||||
object SessionManagementProtocol {
|
object SessionManagementProtocol {
|
||||||
|
|
||||||
@ -59,6 +59,14 @@ object SessionManagementProtocol {
|
|||||||
val registrationID = TextSecurePreferences.getLocalRegistrationId(context) // TODO: It seems wrong to use the local registration ID for this?
|
val registrationID = TextSecurePreferences.getLocalRegistrationId(context) // TODO: It seems wrong to use the local registration ID for this?
|
||||||
val lokiPreKeyBundleDatabase = DatabaseFactory.getLokiPreKeyBundleDatabase(context)
|
val lokiPreKeyBundleDatabase = DatabaseFactory.getLokiPreKeyBundleDatabase(context)
|
||||||
Log.d("Loki", "Received a pre key bundle from: " + content.sender.toString() + ".")
|
Log.d("Loki", "Received a pre key bundle from: " + content.sender.toString() + ".")
|
||||||
|
if (content.dataMessage.isPresent && content.dataMessage.get().isSessionRequest) {
|
||||||
|
val sessionRequestTimestamp = DatabaseFactory.getLokiAPIDatabase(context).getSessionRequestTimestamp(content.sender)
|
||||||
|
if (sessionRequestTimestamp != null && content.timestamp < sessionRequestTimestamp) {
|
||||||
|
// We sent or processed a session request after this one was sent
|
||||||
|
Log.d("Loki", "Ignoring session request from: ${content.sender}.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
val preKeyBundle = preKeyBundleMessage.getPreKeyBundle(registrationID)
|
val preKeyBundle = preKeyBundleMessage.getPreKeyBundle(registrationID)
|
||||||
lokiPreKeyBundleDatabase.setPreKeyBundle(content.sender, preKeyBundle)
|
lokiPreKeyBundleDatabase.setPreKeyBundle(content.sender, preKeyBundle)
|
||||||
}
|
}
|
||||||
@ -66,11 +74,13 @@ object SessionManagementProtocol {
|
|||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun handleSessionRequestIfNeeded(context: Context, content: SignalServiceContent): Boolean {
|
fun handleSessionRequestIfNeeded(context: Context, content: SignalServiceContent): Boolean {
|
||||||
if (!content.dataMessage.isPresent || !content.dataMessage.get().isSessionRequest) { return false }
|
if (!content.dataMessage.isPresent || !content.dataMessage.get().isSessionRequest) { return false }
|
||||||
val sentSessionRequestTimestamp = DatabaseFactory.getLokiAPIDatabase(context).getSessionRequestTimestamp(content.sender)
|
val sessionRequestTimestamp = DatabaseFactory.getLokiAPIDatabase(context).getSessionRequestTimestamp(content.sender)
|
||||||
if (sentSessionRequestTimestamp != null && content.timestamp < sentSessionRequestTimestamp) {
|
if (sessionRequestTimestamp != null && content.timestamp < sessionRequestTimestamp) {
|
||||||
// We sent a session request after this one was sent
|
// We sent or processed a session request after this one was sent
|
||||||
|
Log.d("Loki", "Ignoring session request from: ${content.sender}.")
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
DatabaseFactory.getLokiAPIDatabase(context).setSessionRequestTimestamp(content.sender, Date().time)
|
||||||
val ephemeralMessage = EphemeralMessage.create(content.sender)
|
val ephemeralMessage = EphemeralMessage.create(content.sender)
|
||||||
ApplicationContext.getInstance(context).jobManager.add(PushEphemeralMessageSendJob(ephemeralMessage))
|
ApplicationContext.getInstance(context).jobManager.add(PushEphemeralMessageSendJob(ephemeralMessage))
|
||||||
return true
|
return true
|
||||||
|
@ -14,8 +14,8 @@ import org.whispersystems.signalservice.api.messages.SignalServiceContent
|
|||||||
import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage
|
import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage
|
||||||
import org.whispersystems.signalservice.api.messages.SignalServiceGroup
|
import org.whispersystems.signalservice.api.messages.SignalServiceGroup
|
||||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress
|
import org.whispersystems.signalservice.api.push.SignalServiceAddress
|
||||||
import org.whispersystems.signalservice.loki.api.rssfeeds.LokiRSSFeed
|
import org.whispersystems.signalservice.loki.api.shelved.rssfeeds.LokiRSSFeed
|
||||||
import org.whispersystems.signalservice.loki.api.rssfeeds.LokiRSSFeedProxy
|
import org.whispersystems.signalservice.loki.api.shelved.rssfeeds.LokiRSSFeedProxy
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.regex.Pattern
|
import java.util.regex.Pattern
|
||||||
|
|
||||||
@ -66,7 +66,7 @@ class LokiRSSFeedPoller(private val context: Context, private val feed: LokiRSSF
|
|||||||
val id = feed.id.toByteArray()
|
val id = feed.id.toByteArray()
|
||||||
val x1 = SignalServiceGroup(SignalServiceGroup.Type.UPDATE, id, SignalServiceGroup.GroupType.RSS_FEED, null, null, null, null)
|
val x1 = SignalServiceGroup(SignalServiceGroup.Type.UPDATE, id, SignalServiceGroup.GroupType.RSS_FEED, null, null, null, null)
|
||||||
val x2 = SignalServiceDataMessage(timestamp, x1, null, body)
|
val x2 = SignalServiceDataMessage(timestamp, x1, null, body)
|
||||||
val x3 = SignalServiceContent(x2, "Loki", SignalServiceAddress.DEFAULT_DEVICE_ID, timestamp, false, false, false, false, false)
|
val x3 = SignalServiceContent(x2, "Loki", SignalServiceAddress.DEFAULT_DEVICE_ID, timestamp, false, false, false, false)
|
||||||
PushDecryptJob(context).handleTextMessage(x3, x2, Optional.absent(), Optional.absent())
|
PushDecryptJob(context).handleTextMessage(x3, x2, Optional.absent(), Optional.absent())
|
||||||
}
|
}
|
||||||
}.fail { exception ->
|
}.fail { exception ->
|
||||||
|
@ -10,7 +10,7 @@ import org.thoughtcrime.securesms.ApplicationContext;
|
|||||||
import org.thoughtcrime.securesms.loki.api.LokiPublicChatManager;
|
import org.thoughtcrime.securesms.loki.api.LokiPublicChatManager;
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||||
import org.thoughtcrime.securesms.util.Debouncer;
|
import org.thoughtcrime.securesms.util.Debouncer;
|
||||||
import org.whispersystems.signalservice.loki.api.LokiPoller;
|
import org.whispersystems.signalservice.loki.api.Poller;
|
||||||
|
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
@ -40,9 +40,9 @@ public class OptimizedMessageNotifier implements MessageNotifier {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void updateNotification(@NonNull Context context) {
|
public void updateNotification(@NonNull Context context) {
|
||||||
LokiPoller lokiPoller = ApplicationContext.getInstance(context).lokiPoller;
|
Poller lokiPoller = ApplicationContext.getInstance(context).lokiPoller;
|
||||||
LokiPublicChatManager lokiPublicChatManager = ApplicationContext.getInstance(context).lokiPublicChatManager;
|
LokiPublicChatManager lokiPublicChatManager = ApplicationContext.getInstance(context).lokiPublicChatManager;
|
||||||
Boolean isCaughtUp = false;
|
boolean isCaughtUp = false;
|
||||||
if (lokiPoller != null && lokiPublicChatManager != null) {
|
if (lokiPoller != null && lokiPublicChatManager != null) {
|
||||||
isCaughtUp = lokiPoller.isCaughtUp() && lokiPublicChatManager.areAllCaughtUp();
|
isCaughtUp = lokiPoller.isCaughtUp() && lokiPublicChatManager.areAllCaughtUp();
|
||||||
}
|
}
|
||||||
@ -56,9 +56,9 @@ public class OptimizedMessageNotifier implements MessageNotifier {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void updateNotification(@NonNull Context context, long threadId) {
|
public void updateNotification(@NonNull Context context, long threadId) {
|
||||||
LokiPoller lokiPoller = ApplicationContext.getInstance(context).lokiPoller;
|
Poller lokiPoller = ApplicationContext.getInstance(context).lokiPoller;
|
||||||
LokiPublicChatManager lokiPublicChatManager = ApplicationContext.getInstance(context).lokiPublicChatManager;
|
LokiPublicChatManager lokiPublicChatManager = ApplicationContext.getInstance(context).lokiPublicChatManager;
|
||||||
Boolean isCaughtUp = false;
|
boolean isCaughtUp = false;
|
||||||
if (lokiPoller != null && lokiPublicChatManager != null) {
|
if (lokiPoller != null && lokiPublicChatManager != null) {
|
||||||
isCaughtUp = lokiPoller.isCaughtUp() && lokiPublicChatManager.areAllCaughtUp();
|
isCaughtUp = lokiPoller.isCaughtUp() && lokiPublicChatManager.areAllCaughtUp();
|
||||||
}
|
}
|
||||||
@ -72,9 +72,9 @@ public class OptimizedMessageNotifier implements MessageNotifier {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void updateNotification(@NonNull Context context, long threadId, boolean signal) {
|
public void updateNotification(@NonNull Context context, long threadId, boolean signal) {
|
||||||
LokiPoller lokiPoller = ApplicationContext.getInstance(context).lokiPoller;
|
Poller lokiPoller = ApplicationContext.getInstance(context).lokiPoller;
|
||||||
LokiPublicChatManager lokiPublicChatManager = ApplicationContext.getInstance(context).lokiPublicChatManager;
|
LokiPublicChatManager lokiPublicChatManager = ApplicationContext.getInstance(context).lokiPublicChatManager;
|
||||||
Boolean isCaughtUp = false;
|
boolean isCaughtUp = false;
|
||||||
if (lokiPoller != null && lokiPublicChatManager != null) {
|
if (lokiPoller != null && lokiPublicChatManager != null) {
|
||||||
isCaughtUp = lokiPoller.isCaughtUp() && lokiPublicChatManager.areAllCaughtUp();
|
isCaughtUp = lokiPoller.isCaughtUp() && lokiPublicChatManager.areAllCaughtUp();
|
||||||
}
|
}
|
||||||
@ -88,9 +88,9 @@ public class OptimizedMessageNotifier implements MessageNotifier {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void updateNotification(@android.support.annotation.NonNull Context context, boolean signal, int reminderCount) {
|
public void updateNotification(@android.support.annotation.NonNull Context context, boolean signal, int reminderCount) {
|
||||||
LokiPoller lokiPoller = ApplicationContext.getInstance(context).lokiPoller;
|
Poller lokiPoller = ApplicationContext.getInstance(context).lokiPoller;
|
||||||
LokiPublicChatManager lokiPublicChatManager = ApplicationContext.getInstance(context).lokiPublicChatManager;
|
LokiPublicChatManager lokiPublicChatManager = ApplicationContext.getInstance(context).lokiPublicChatManager;
|
||||||
Boolean isCaughtUp = false;
|
boolean isCaughtUp = false;
|
||||||
if (lokiPoller != null && lokiPublicChatManager != null) {
|
if (lokiPoller != null && lokiPublicChatManager != null) {
|
||||||
isCaughtUp = lokiPoller.isCaughtUp() && lokiPublicChatManager.areAllCaughtUp();
|
isCaughtUp = lokiPoller.isCaughtUp() && lokiPublicChatManager.areAllCaughtUp();
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user