use of org.whispersystems.libsignal.util.Pair in project Signal-Android by signalapp.
the class MmsSmsDatabase method setTimestampRead.
public void setTimestampRead(@NonNull Recipient senderRecipient, @NonNull List<ReadMessage> readMessages, long proposedExpireStarted, @NonNull Map<Long, Long> threadToLatestRead) {
SQLiteDatabase db = getWritableDatabase();
List<Pair<Long, Long>> expiringText = new LinkedList<>();
List<Pair<Long, Long>> expiringMedia = new LinkedList<>();
Set<Long> updatedThreads = new HashSet<>();
db.beginTransaction();
try {
for (ReadMessage readMessage : readMessages) {
TimestampReadResult textResult = SignalDatabase.sms().setTimestampRead(new SyncMessageId(senderRecipient.getId(), readMessage.getTimestamp()), proposedExpireStarted, threadToLatestRead);
TimestampReadResult mediaResult = SignalDatabase.mms().setTimestampRead(new SyncMessageId(senderRecipient.getId(), readMessage.getTimestamp()), proposedExpireStarted, threadToLatestRead);
expiringText.addAll(textResult.expiring);
expiringMedia.addAll(mediaResult.expiring);
updatedThreads.addAll(textResult.threads);
updatedThreads.addAll(mediaResult.threads);
}
for (long threadId : updatedThreads) {
SignalDatabase.threads().updateReadState(threadId);
SignalDatabase.threads().setLastSeen(threadId);
}
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
for (Pair<Long, Long> expiringMessage : expiringText) {
ApplicationDependencies.getExpiringMessageManager().scheduleDeletion(expiringMessage.first(), false, proposedExpireStarted, expiringMessage.second());
}
for (Pair<Long, Long> expiringMessage : expiringMedia) {
ApplicationDependencies.getExpiringMessageManager().scheduleDeletion(expiringMessage.first(), true, proposedExpireStarted, expiringMessage.second());
}
for (long threadId : updatedThreads) {
notifyConversationListeners(threadId);
}
}
use of org.whispersystems.libsignal.util.Pair in project Signal-Android by signalapp.
the class DirectoryHelper method filterForUnlistedUsers.
/**
* Users can mark themselves as 'unlisted' in CDS, meaning that even if CDS says they're
* unregistered, they might actually be registered. We need to double-check users who we already
* have UUIDs for. Also, we only want to bother doing this for users we have conversations for,
* so we will also only check for users that have a thread.
*/
private static UnlistedResult filterForUnlistedUsers(@NonNull Context context, @NonNull Set<RecipientId> inactiveIds) {
List<Recipient> possiblyUnlisted = Stream.of(inactiveIds).map(Recipient::resolved).filter(Recipient::isRegistered).filter(Recipient::hasServiceId).filter(DirectoryHelper::hasCommunicatedWith).toList();
ProfileService profileService = new ProfileService(ApplicationDependencies.getGroupsV2Operations().getProfileOperations(), ApplicationDependencies.getSignalServiceMessageReceiver(), ApplicationDependencies.getSignalWebSocket());
List<Observable<Pair<Recipient, ServiceResponse<ProfileAndCredential>>>> requests = Stream.of(possiblyUnlisted).map(r -> ProfileUtil.retrieveProfile(context, r, SignalServiceProfile.RequestType.PROFILE, profileService).toObservable().timeout(5, TimeUnit.SECONDS).onErrorReturn(t -> new Pair<>(r, ServiceResponse.forUnknownError(t)))).toList();
return Observable.mergeDelayError(requests).observeOn(Schedulers.io(), true).scan(new UnlistedResult.Builder(), (builder, pair) -> {
Recipient recipient = pair.first();
ProfileService.ProfileResponseProcessor processor = new ProfileService.ProfileResponseProcessor(pair.second());
if (processor.hasResult()) {
builder.potentiallyActiveIds.add(recipient.getId());
} else if (processor.genericIoError() || !processor.notFound()) {
builder.retries.add(recipient.getId());
builder.potentiallyActiveIds.add(recipient.getId());
}
return builder;
}).lastOrError().map(UnlistedResult.Builder::build).blockingGet();
}
use of org.whispersystems.libsignal.util.Pair in project Signal-Android by signalapp.
the class PushGroupSendJob method onPushSend.
@Override
public void onPushSend() throws IOException, MmsException, NoSuchMessageException, RetryLaterException {
SignalLocalMetrics.GroupMessageSend.onJobStarted(messageId);
MessageDatabase database = SignalDatabase.mms();
OutgoingMediaMessage message = database.getOutgoingMessage(messageId);
long threadId = database.getMessageRecord(messageId).getThreadId();
Set<NetworkFailure> existingNetworkFailures = message.getNetworkFailures();
Set<IdentityKeyMismatch> existingIdentityMismatches = message.getIdentityKeyMismatches();
ApplicationDependencies.getJobManager().cancelAllInQueue(TypingSendJob.getQueue(threadId));
if (database.isSent(messageId)) {
log(TAG, String.valueOf(message.getSentTimeMillis()), "Message " + messageId + " was already sent. Ignoring.");
return;
}
Recipient groupRecipient = message.getRecipient().resolve();
if (!groupRecipient.isPushGroup()) {
throw new MmsException("Message recipient isn't a group!");
}
if (groupRecipient.isPushV1Group()) {
throw new MmsException("No GV1 messages can be sent anymore!");
}
try {
log(TAG, String.valueOf(message.getSentTimeMillis()), "Sending message: " + messageId + ", Recipient: " + message.getRecipient().getId() + ", Thread: " + threadId + ", Attachments: " + buildAttachmentString(message.getAttachments()));
if (!groupRecipient.resolve().isProfileSharing() && !database.isGroupQuitMessage(messageId)) {
RecipientUtil.shareProfileIfFirstSecureMessage(context, groupRecipient);
}
List<Recipient> target;
if (filterRecipient != null)
target = Collections.singletonList(Recipient.resolved(filterRecipient));
else if (!existingNetworkFailures.isEmpty())
target = Stream.of(existingNetworkFailures).map(nf -> nf.getRecipientId(context)).distinct().map(Recipient::resolved).toList();
else
target = Stream.of(getGroupMessageRecipients(groupRecipient.requireGroupId(), messageId)).distinctBy(Recipient::getId).toList();
RecipientAccessList accessList = new RecipientAccessList(target);
List<SendMessageResult> results = deliver(message, groupRecipient, target);
Log.i(TAG, JobLogger.format(this, "Finished send."));
List<NetworkFailure> networkFailures = Stream.of(results).filter(SendMessageResult::isNetworkFailure).map(result -> new NetworkFailure(accessList.requireIdByAddress(result.getAddress()))).toList();
List<IdentityKeyMismatch> identityMismatches = Stream.of(results).filter(result -> result.getIdentityFailure() != null).map(result -> new IdentityKeyMismatch(accessList.requireIdByAddress(result.getAddress()), result.getIdentityFailure().getIdentityKey())).toList();
ProofRequiredException proofRequired = Stream.of(results).filter(r -> r.getProofRequiredFailure() != null).findLast().map(SendMessageResult::getProofRequiredFailure).orElse(null);
List<SendMessageResult> successes = Stream.of(results).filter(result -> result.getSuccess() != null).toList();
List<Pair<RecipientId, Boolean>> successUnidentifiedStatus = Stream.of(successes).map(result -> new Pair<>(accessList.requireIdByAddress(result.getAddress()), result.getSuccess().isUnidentified())).toList();
Set<RecipientId> successIds = Stream.of(successUnidentifiedStatus).map(Pair::first).collect(Collectors.toSet());
List<NetworkFailure> resolvedNetworkFailures = Stream.of(existingNetworkFailures).filter(failure -> successIds.contains(failure.getRecipientId(context))).toList();
List<IdentityKeyMismatch> resolvedIdentityFailures = Stream.of(existingIdentityMismatches).filter(failure -> successIds.contains(failure.getRecipientId(context))).toList();
List<RecipientId> unregisteredRecipients = Stream.of(results).filter(SendMessageResult::isUnregisteredFailure).map(result -> RecipientId.from(result.getAddress())).toList();
if (networkFailures.size() > 0 || identityMismatches.size() > 0 || proofRequired != null || unregisteredRecipients.size() > 0) {
Log.w(TAG, String.format(Locale.US, "Failed to send to some recipients. Network: %d, Identity: %d, ProofRequired: %s, Unregistered: %d", networkFailures.size(), identityMismatches.size(), proofRequired != null, unregisteredRecipients.size()));
}
RecipientDatabase recipientDatabase = SignalDatabase.recipients();
for (RecipientId unregistered : unregisteredRecipients) {
recipientDatabase.markUnregistered(unregistered);
}
existingNetworkFailures.removeAll(resolvedNetworkFailures);
existingNetworkFailures.addAll(networkFailures);
database.setNetworkFailures(messageId, existingNetworkFailures);
existingIdentityMismatches.removeAll(resolvedIdentityFailures);
existingIdentityMismatches.addAll(identityMismatches);
database.setMismatchedIdentities(messageId, existingIdentityMismatches);
SignalDatabase.groupReceipts().setUnidentified(successUnidentifiedStatus, messageId);
if (proofRequired != null) {
handleProofRequiredException(proofRequired, groupRecipient, threadId, messageId, true);
}
if (existingNetworkFailures.isEmpty() && networkFailures.isEmpty() && identityMismatches.isEmpty() && existingIdentityMismatches.isEmpty()) {
database.markAsSent(messageId, true);
markAttachmentsUploaded(messageId, message);
if (message.getExpiresIn() > 0 && !message.isExpirationUpdate()) {
database.markExpireStarted(messageId);
ApplicationDependencies.getExpiringMessageManager().scheduleDeletion(messageId, true, message.getExpiresIn());
}
if (message.isViewOnce()) {
SignalDatabase.attachments().deleteAttachmentFilesForViewOnceMessage(messageId);
}
} else if (!identityMismatches.isEmpty()) {
Log.w(TAG, "Failing because there were " + identityMismatches.size() + " identity mismatches.");
database.markAsSentFailed(messageId);
notifyMediaMessageDeliveryFailed(context, messageId);
Set<RecipientId> mismatchRecipientIds = Stream.of(identityMismatches).map(mismatch -> mismatch.getRecipientId(context)).collect(Collectors.toSet());
RetrieveProfileJob.enqueue(mismatchRecipientIds);
} else if (!networkFailures.isEmpty()) {
Log.w(TAG, "Retrying because there were " + networkFailures.size() + " network failures.");
throw new RetryLaterException();
}
} catch (UntrustedIdentityException | UndeliverableMessageException e) {
warn(TAG, String.valueOf(message.getSentTimeMillis()), e);
database.markAsSentFailed(messageId);
notifyMediaMessageDeliveryFailed(context, messageId);
}
SignalLocalMetrics.GroupMessageSend.onJobFinished(messageId);
}
use of org.whispersystems.libsignal.util.Pair in project Signal-Android by signalapp.
the class ChunkedDataFetcher method fetchChunks.
private void fetchChunks(@NonNull String url, long contentLength, Optional<Pair<InputStream, Long>> firstChunk, CompositeRequestController compositeController, Callback callback) {
List<ByteRange> requestPattern;
try {
if (firstChunk.isPresent()) {
requestPattern = Stream.of(getRequestPattern(contentLength - firstChunk.get().second())).map(b -> new ByteRange(b.start + firstChunk.get().second(), b.end + firstChunk.get().second(), b.ignoreFirst)).toList();
} else {
requestPattern = getRequestPattern(contentLength);
}
} catch (IOException e) {
callback.onFailure(e);
compositeController.cancel();
return;
}
SignalExecutors.UNBOUNDED.execute(() -> {
List<CallRequestController> controllers = Stream.of(requestPattern).map(range -> makeChunkRequest(client, url, range)).toList();
List<InputStream> streams = new ArrayList<>(controllers.size() + (firstChunk.isPresent() ? 1 : 0));
if (firstChunk.isPresent()) {
streams.add(firstChunk.get().first());
}
Stream.of(controllers).forEach(compositeController::addController);
for (CallRequestController controller : controllers) {
Optional<InputStream> stream = controller.getStream();
if (!stream.isPresent()) {
Log.w(TAG, "Stream was canceled.");
callback.onFailure(new IOException("Failure"));
compositeController.cancel();
return;
}
streams.add(stream.get());
}
try {
callback.onSuccess(new InputStreamList(streams));
} catch (IOException e) {
callback.onFailure(e);
compositeController.cancel();
}
});
}
use of org.whispersystems.libsignal.util.Pair in project Signal-Android by signalapp.
the class MarkReadReceiverTest method givenMultipleThreadsWithMultipleMessagesEach_whenIProcess_thenIProperlyGroupByThreadAndRecipient.
@Test
public void givenMultipleThreadsWithMultipleMessagesEach_whenIProcess_thenIProperlyGroupByThreadAndRecipient() {
// GIVEN
List<RecipientId> recipients = Stream.range(1L, 4L).map(RecipientId::from).toList();
List<Long> threads = Stream.range(4L, 7L).toList();
int expected = recipients.size() * threads.size() + 1;
List<MessageDatabase.MarkedMessageInfo> infoList = Stream.of(threads).flatMap(threadId -> Stream.of(recipients).map(recipientId -> createMarkedMessageInfo(threadId, recipientId))).toList();
List<MessageDatabase.MarkedMessageInfo> duplicatedList = Util.concatenatedList(infoList, infoList);
// WHEN
MarkReadReceiver.process(mockContext, duplicatedList);
// THEN
assertEquals("Should have 10 total jobs, including MultiDeviceReadUpdateJob", expected, jobs.size());
Set<Pair<Long, String>> threadRecipientPairs = new HashSet<>();
Stream.of(jobs).forEach(job -> {
if (job instanceof MultiDeviceReadUpdateJob) {
return;
}
Data data = job.serialize();
long threadId = data.getLong("thread");
String recipientId = data.getString("recipient");
long[] messageIds = data.getLongArray("message_ids");
assertEquals("Each job should contain two messages.", 2, messageIds.length);
assertTrue("Each thread recipient pair should only exist once.", threadRecipientPairs.add(new Pair<>(threadId, recipientId)));
});
assertEquals("Should have 9 total combinations.", 9, threadRecipientPairs.size());
}
Aggregations