import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.security.SignatureException;
+import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
private Thread receiveThread;
private final Set<ReceiveMessageHandler> weakHandlers = new HashSet<>();
private final Set<ReceiveMessageHandler> messageHandlers = new HashSet<>();
+ private final List<Runnable> closedListeners = new ArrayList<>();
private boolean isReceivingSynchronous;
ManagerImpl(
this.serviceEnvironmentConfig = serviceEnvironmentConfig;
final var credentialsProvider = new DynamicCredentialsProvider(account.getAci(),
- account.getUsername(),
+ account.getAccount(),
account.getPassword(),
account.getDeviceId());
final var sessionLock = new SignalSessionLock() {
@Override
public String getSelfNumber() {
- return account.getUsername();
+ return account.getAccount();
}
@Override
public Map<String, Pair<String, UUID>> areUsersRegistered(Set<String> numbers) throws IOException {
Map<String, String> canonicalizedNumbers = numbers.stream().collect(Collectors.toMap(n -> n, n -> {
try {
- return PhoneNumberFormatter.formatNumber(n, account.getUsername());
+ return PhoneNumberFormatter.formatNumber(n, account.getAccount());
} catch (InvalidNumberException e) {
return "";
}
dependencies.getAccountManager().setGcmId(Optional.absent());
account.setRegistered(false);
+ close();
}
@Override
dependencies.getAccountManager().deleteAccount();
account.setRegistered(false);
+ close();
}
@Override
public void submitRateLimitRecaptchaChallenge(String challenge, String captcha) throws IOException {
+ captcha = captcha == null ? null : captcha.replace("signalcaptcha://", "");
+
dependencies.getAccountManager().submitRateLimitRecaptchaChallenge(challenge, captcha);
}
if (attachments != null) {
messageBuilder.withAttachments(attachmentHelper.uploadAttachments(attachments));
}
+ if (message.mentions().size() > 0) {
+ final var mentions = new ArrayList<SignalServiceDataMessage.Mention>();
+ for (final var m : message.mentions()) {
+ final var recipientId = resolveRecipient(m.recipient());
+ mentions.add(new SignalServiceDataMessage.Mention(resolveSignalServiceAddress(recipientId).getAci(),
+ m.start(),
+ m.length()));
+ }
+ messageBuilder.withMentions(mentions);
+ }
}
@Override
) throws IOException {
retryFailedReceivedMessages(handler);
- Set<HandleAction> queuedActions = new HashSet<>();
+ // Use a Map here because java Set doesn't have a get method ...
+ Map<HandleAction, HandleAction> queuedActions = new HashMap<>();
final var signalWebSocket = dependencies.getSignalWebSocket();
signalWebSocket.connect();
while (!Thread.interrupted()) {
SignalServiceEnvelope envelope;
final CachedMessage[] cachedMessage = {null};
- account.setLastReceiveTimestamp(System.currentTimeMillis());
+ final var nowMillis = System.currentTimeMillis();
+ if (nowMillis - account.getLastReceiveTimestamp() > 60000) {
+ account.setLastReceiveTimestamp(nowMillis);
+ }
logger.debug("Checking for new message from server");
try {
var result = signalWebSocket.readOrEmpty(unit.toMillis(timeout), envelope1 -> {
logger.debug("New message received from server");
} else {
logger.debug("Received indicator that server queue is empty");
- handleQueuedActions(queuedActions);
+ handleQueuedActions(queuedActions.keySet());
queuedActions.clear();
hasCaughtUpWithOldMessages = true;
}
final var result = incomingMessageHandler.handleEnvelope(envelope, ignoreAttachments, handler);
- queuedActions.addAll(result.first());
+ for (final var h : result.first()) {
+ final var existingAction = queuedActions.get(h);
+ if (existingAction == null) {
+ queuedActions.put(h, h);
+ } else {
+ existingAction.mergeOther(h);
+ }
+ }
final var exception = result.second();
if (hasCaughtUpWithOldMessages) {
- handleQueuedActions(queuedActions);
+ handleQueuedActions(queuedActions.keySet());
queuedActions.clear();
}
if (cachedMessage[0] != null) {
}
}
}
- handleQueuedActions(queuedActions);
+ handleQueuedActions(queuedActions.keySet());
queuedActions.clear();
dependencies.getSignalWebSocket().disconnect();
}
/**
* Trust this the identity with this fingerprint
*
- * @param recipient username of the identity
+ * @param recipient account of the identity
* @param fingerprint Fingerprint
*/
@Override
/**
* Trust this the identity with this safety number
*
- * @param recipient username of the identity
+ * @param recipient account of the identity
* @param safetyNumber Safety number
*/
@Override
/**
* Trust this the identity with this scannable safety number
*
- * @param recipient username of the identity
+ * @param recipient account of the identity
* @param safetyNumber Scannable safety number
*/
@Override
/**
* Trust all keys of this identity without verification
*
- * @param recipient username of the identity
+ * @param recipient account of the identity
*/
@Override
public boolean trustIdentityAllKeys(RecipientIdentifier.Single recipient) {
return identityHelper.trustIdentityAllKeys(recipientId);
}
+ @Override
+ public void addClosedListener(final Runnable listener) {
+ synchronized (closedListeners) {
+ closedListeners.add(listener);
+ }
+ }
+
private void handleIdentityFailure(
final RecipientId recipientId,
final org.whispersystems.signalservice.api.messages.SendMessageResult.IdentityFailure identityFailure
@Override
public void close() throws IOException {
- close(true);
- }
-
- private void close(boolean closeAccount) throws IOException {
Thread thread;
synchronized (messageHandlers) {
weakHandlers.clear();
dependencies.getSignalWebSocket().disconnect();
- if (closeAccount && account != null) {
+ synchronized (closedListeners) {
+ closedListeners.forEach(Runnable::run);
+ closedListeners.clear();
+ }
+
+ if (account != null) {
account.close();
}
account = null;