Search in sources :

Example 16 with Pair

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);
    }
}
Also used : ReadMessage(org.whispersystems.signalservice.api.messages.multidevice.ReadMessage) SyncMessageId(org.thoughtcrime.securesms.database.MessageDatabase.SyncMessageId) LinkedList(java.util.LinkedList) Pair(org.whispersystems.libsignal.util.Pair) HashSet(java.util.HashSet)

Example 17 with Pair

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();
}
Also used : SignalStore(org.thoughtcrime.securesms.keyvalue.SignalStore) NonNull(androidx.annotation.NonNull) R(org.thoughtcrime.securesms.R) SignalServiceAddress(org.whispersystems.signalservice.api.push.SignalServiceAddress) Manifest(android.Manifest) ProfileAndCredential(org.whispersystems.signalservice.api.profiles.ProfileAndCredential) ContactsContract(android.provider.ContactsContract) BulkOperationsHandle(org.thoughtcrime.securesms.database.RecipientDatabase.BulkOperationsHandle) RegisteredState(org.thoughtcrime.securesms.database.RecipientDatabase.RegisteredState) RecipientId(org.thoughtcrime.securesms.recipients.RecipientId) StorageSyncHelper(org.thoughtcrime.securesms.storage.StorageSyncHelper) ContentResolver(android.content.ContentResolver) Map(java.util.Map) SignalProtocolAddress(org.whispersystems.libsignal.SignalProtocolAddress) Recipient(org.thoughtcrime.securesms.recipients.Recipient) AccountManager(android.accounts.AccountManager) ACI(org.whispersystems.signalservice.api.push.ACI) Account(android.accounts.Account) ApplicationDependencies(org.thoughtcrime.securesms.dependencies.ApplicationDependencies) Collection(java.util.Collection) Set(java.util.Set) SetUtil(org.thoughtcrime.securesms.util.SetUtil) Objects(java.util.Objects) Log(org.signal.core.util.logging.Log) FeatureFlags(org.thoughtcrime.securesms.util.FeatureFlags) List(java.util.List) Nullable(androidx.annotation.Nullable) StorageSyncJob(org.thoughtcrime.securesms.jobs.StorageSyncJob) ProfileService(org.whispersystems.signalservice.api.services.ProfileService) BuildConfig(org.thoughtcrime.securesms.BuildConfig) InsertResult(org.thoughtcrime.securesms.database.MessageDatabase.InsertResult) Context(android.content.Context) SignalDatabase(org.thoughtcrime.securesms.database.SignalDatabase) Stream(com.annimon.stream.Stream) Util(org.thoughtcrime.securesms.util.Util) WorkerThread(androidx.annotation.WorkerThread) RemoteException(android.os.RemoteException) RetrieveProfileJob(org.thoughtcrime.securesms.jobs.RetrieveProfileJob) RecipientDatabase(org.thoughtcrime.securesms.database.RecipientDatabase) SignalServiceProfile(org.whispersystems.signalservice.api.profiles.SignalServiceProfile) TextSecurePreferences(org.thoughtcrime.securesms.util.TextSecurePreferences) HashSet(java.util.HashSet) Schedulers(io.reactivex.rxjava3.schedulers.Schedulers) Pair(org.whispersystems.libsignal.util.Pair) NotificationChannels(org.thoughtcrime.securesms.notifications.NotificationChannels) ProfileUtil(org.thoughtcrime.securesms.util.ProfileUtil) Calendar(java.util.Calendar) Observable(io.reactivex.rxjava3.core.Observable) RegistrationUtil(org.thoughtcrime.securesms.registration.RegistrationUtil) ContactsDatabase(org.thoughtcrime.securesms.contacts.ContactsDatabase) MultiDeviceContactUpdateJob(org.thoughtcrime.securesms.jobs.MultiDeviceContactUpdateJob) Cursor(android.database.Cursor) Collectors(com.annimon.stream.Collectors) Permissions(org.thoughtcrime.securesms.permissions.Permissions) UuidUtil(org.whispersystems.signalservice.api.util.UuidUtil) TextUtils(android.text.TextUtils) IOException(java.io.IOException) ServiceResponse(org.whispersystems.signalservice.internal.ServiceResponse) OperationApplicationException(android.content.OperationApplicationException) Optional(org.whispersystems.libsignal.util.guava.Optional) TimeUnit(java.util.concurrent.TimeUnit) CursorUtil(org.thoughtcrime.securesms.util.CursorUtil) IncomingJoinedMessage(org.thoughtcrime.securesms.sms.IncomingJoinedMessage) ContactAccessor(org.thoughtcrime.securesms.contacts.ContactAccessor) Stopwatch(org.thoughtcrime.securesms.util.Stopwatch) PhoneNumberFormatter(org.thoughtcrime.securesms.phonenumbers.PhoneNumberFormatter) Collections(java.util.Collections) ServiceResponse(org.whispersystems.signalservice.internal.ServiceResponse) ProfileService(org.whispersystems.signalservice.api.services.ProfileService) Recipient(org.thoughtcrime.securesms.recipients.Recipient) Observable(io.reactivex.rxjava3.core.Observable)

Example 18 with Pair

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);
}
Also used : ServerRejectedException(org.whispersystems.signalservice.api.push.exceptions.ServerRejectedException) SignalServiceDataMessage(org.whispersystems.signalservice.api.messages.SignalServiceDataMessage) SendMessageResult(org.whispersystems.signalservice.api.messages.SendMessageResult) NonNull(androidx.annotation.NonNull) Data(org.thoughtcrime.securesms.jobmanager.Data) JobManager(org.thoughtcrime.securesms.jobmanager.JobManager) RecipientUtil(org.thoughtcrime.securesms.recipients.RecipientUtil) Quote(org.whispersystems.signalservice.api.messages.SignalServiceDataMessage.Quote) MessageGroupContext(org.thoughtcrime.securesms.mms.MessageGroupContext) RecipientId(org.thoughtcrime.securesms.recipients.RecipientId) Locale(java.util.Locale) Recipient(org.thoughtcrime.securesms.recipients.Recipient) OutgoingGroupUpdateMessage(org.thoughtcrime.securesms.mms.OutgoingGroupUpdateMessage) ApplicationDependencies(org.thoughtcrime.securesms.dependencies.ApplicationDependencies) SignalServiceAttachment(org.whispersystems.signalservice.api.messages.SignalServiceAttachment) Set(java.util.Set) GroupDatabase(org.thoughtcrime.securesms.database.GroupDatabase) UndeliverableMessageException(org.thoughtcrime.securesms.transport.UndeliverableMessageException) ByteString(com.google.protobuf.ByteString) Log(org.signal.core.util.logging.Log) GroupContextV2(org.whispersystems.signalservice.internal.push.SignalServiceProtos.GroupContextV2) List(java.util.List) Nullable(androidx.annotation.Nullable) SignalServiceGroupV2(org.whispersystems.signalservice.api.messages.SignalServiceGroupV2) GroupId(org.thoughtcrime.securesms.groups.GroupId) Job(org.thoughtcrime.securesms.jobmanager.Job) SharedContact(org.whispersystems.signalservice.api.messages.shared.SharedContact) MessageDatabase(org.thoughtcrime.securesms.database.MessageDatabase) GroupUtil(org.thoughtcrime.securesms.util.GroupUtil) NoSuchMessageException(org.thoughtcrime.securesms.database.NoSuchMessageException) Attachment(org.thoughtcrime.securesms.attachments.Attachment) OutgoingMediaMessage(org.thoughtcrime.securesms.mms.OutgoingMediaMessage) Preview(org.whispersystems.signalservice.api.messages.SignalServiceDataMessage.Preview) Context(android.content.Context) RecipientAccessList(org.thoughtcrime.securesms.util.RecipientAccessList) SignalDatabase(org.thoughtcrime.securesms.database.SignalDatabase) RetryLaterException(org.thoughtcrime.securesms.transport.RetryLaterException) ContentHint(org.whispersystems.signalservice.api.crypto.ContentHint) Stream(com.annimon.stream.Stream) JobLogger(org.thoughtcrime.securesms.jobmanager.JobLogger) SignalLocalMetrics(org.thoughtcrime.securesms.util.SignalLocalMetrics) WorkerThread(androidx.annotation.WorkerThread) RecipientDatabase(org.thoughtcrime.securesms.database.RecipientDatabase) Pair(org.whispersystems.libsignal.util.Pair) GroupSendUtil(org.thoughtcrime.securesms.messages.GroupSendUtil) GroupReceiptInfo(org.thoughtcrime.securesms.database.GroupReceiptDatabase.GroupReceiptInfo) MessageId(org.thoughtcrime.securesms.database.model.MessageId) Collectors(com.annimon.stream.Collectors) GroupReceiptDatabase(org.thoughtcrime.securesms.database.GroupReceiptDatabase) IdentityKeyMismatch(org.thoughtcrime.securesms.database.documents.IdentityKeyMismatch) MmsException(org.thoughtcrime.securesms.mms.MmsException) NetworkConstraint(org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint) IOException(java.io.IOException) Optional(org.whispersystems.libsignal.util.guava.Optional) TimeUnit(java.util.concurrent.TimeUnit) ProofRequiredException(org.whispersystems.signalservice.api.push.exceptions.ProofRequiredException) UntrustedIdentityException(org.whispersystems.signalservice.api.crypto.UntrustedIdentityException) NetworkFailure(org.thoughtcrime.securesms.database.documents.NetworkFailure) Collections(java.util.Collections) RecipientId(org.thoughtcrime.securesms.recipients.RecipientId) Set(java.util.Set) UntrustedIdentityException(org.whispersystems.signalservice.api.crypto.UntrustedIdentityException) OutgoingMediaMessage(org.thoughtcrime.securesms.mms.OutgoingMediaMessage) RecipientDatabase(org.thoughtcrime.securesms.database.RecipientDatabase) MmsException(org.thoughtcrime.securesms.mms.MmsException) UndeliverableMessageException(org.thoughtcrime.securesms.transport.UndeliverableMessageException) Pair(org.whispersystems.libsignal.util.Pair) MessageDatabase(org.thoughtcrime.securesms.database.MessageDatabase) Recipient(org.thoughtcrime.securesms.recipients.Recipient) ProofRequiredException(org.whispersystems.signalservice.api.push.exceptions.ProofRequiredException) NetworkFailure(org.thoughtcrime.securesms.database.documents.NetworkFailure) IdentityKeyMismatch(org.thoughtcrime.securesms.database.documents.IdentityKeyMismatch) SendMessageResult(org.whispersystems.signalservice.api.messages.SendMessageResult) RetryLaterException(org.thoughtcrime.securesms.transport.RetryLaterException) RecipientAccessList(org.thoughtcrime.securesms.util.RecipientAccessList)

Example 19 with Pair

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();
        }
    });
}
Also used : Request(okhttp3.Request) Stream(com.annimon.stream.Stream) Util(org.thoughtcrime.securesms.util.Util) NonNull(androidx.annotation.NonNull) TextUtils(android.text.TextUtils) ContentLengthInputStream(com.bumptech.glide.util.ContentLengthInputStream) IOException(java.io.IOException) CacheControl(okhttp3.CacheControl) Optional(org.whispersystems.libsignal.util.guava.Optional) ArrayList(java.util.ArrayList) SecureRandom(java.security.SecureRandom) Log(org.signal.core.util.logging.Log) Pair(org.whispersystems.libsignal.util.Pair) FilterInputStream(java.io.FilterInputStream) List(java.util.List) OkHttpClient(okhttp3.OkHttpClient) Response(okhttp3.Response) Call(okhttp3.Call) LinkedList(java.util.LinkedList) SignalExecutors(org.signal.core.util.concurrent.SignalExecutors) InputStream(java.io.InputStream) ContentLengthInputStream(com.bumptech.glide.util.ContentLengthInputStream) FilterInputStream(java.io.FilterInputStream) InputStream(java.io.InputStream) ArrayList(java.util.ArrayList) IOException(java.io.IOException)

Example 20 with Pair

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());
}
Also used : Context(android.content.Context) Stream(com.annimon.stream.Stream) Util(org.thoughtcrime.securesms.util.Util) PowerMockito.mockStatic(org.powermock.api.mockito.PowerMockito.mockStatic) NonNull(androidx.annotation.NonNull) Data(org.thoughtcrime.securesms.jobmanager.Data) JobManager(org.thoughtcrime.securesms.jobmanager.JobManager) RunWith(org.junit.runner.RunWith) HashSet(java.util.HashSet) Answer(org.mockito.stubbing.Answer) Pair(org.whispersystems.libsignal.util.Pair) RecipientId(org.thoughtcrime.securesms.recipients.RecipientId) PrepareForTest(org.powermock.core.classloader.annotations.PrepareForTest) Recipient(org.thoughtcrime.securesms.recipients.Recipient) PowerMockRunner(org.powermock.modules.junit4.PowerMockRunner) LinkedList(java.util.LinkedList) Before(org.junit.Before) MessageId(org.thoughtcrime.securesms.database.model.MessageId) PowerMockito.when(org.powermock.api.mockito.PowerMockito.when) ApplicationDependencies(org.thoughtcrime.securesms.dependencies.ApplicationDependencies) Set(java.util.Set) Assert.assertTrue(org.junit.Assert.assertTrue) Test(org.junit.Test) Matchers.any(org.mockito.Matchers.any) List(java.util.List) PowerMockito.mock(org.powermock.api.mockito.PowerMockito.mock) MultiDeviceReadUpdateJob(org.thoughtcrime.securesms.jobs.MultiDeviceReadUpdateJob) PowerMockito.doAnswer(org.powermock.api.mockito.PowerMockito.doAnswer) Job(org.thoughtcrime.securesms.jobmanager.Job) MessageDatabase(org.thoughtcrime.securesms.database.MessageDatabase) Assert.assertEquals(org.junit.Assert.assertEquals) RecipientId(org.thoughtcrime.securesms.recipients.RecipientId) Data(org.thoughtcrime.securesms.jobmanager.Data) MultiDeviceReadUpdateJob(org.thoughtcrime.securesms.jobs.MultiDeviceReadUpdateJob) Pair(org.whispersystems.libsignal.util.Pair) HashSet(java.util.HashSet) PrepareForTest(org.powermock.core.classloader.annotations.PrepareForTest) Test(org.junit.Test)

Aggregations

Pair (org.whispersystems.libsignal.util.Pair)49 NonNull (androidx.annotation.NonNull)26 List (java.util.List)18 LinkedList (java.util.LinkedList)15 Context (android.content.Context)14 Stream (com.annimon.stream.Stream)14 IOException (java.io.IOException)14 Recipient (org.thoughtcrime.securesms.recipients.Recipient)14 RecipientId (org.thoughtcrime.securesms.recipients.RecipientId)14 Nullable (androidx.annotation.Nullable)12 SpannableString (android.text.SpannableString)10 TextUtils (android.text.TextUtils)10 ArrayList (java.util.ArrayList)10 Collections (java.util.Collections)10 Cursor (android.database.Cursor)9 ContentValues (android.content.ContentValues)8 HashSet (java.util.HashSet)8 Locale (java.util.Locale)8 Set (java.util.Set)8 Log (org.signal.core.util.logging.Log)8